diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 9f146a457fb..189a6669ec1 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -55,7 +55,6 @@
-
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
deleted file mode 100644
index 7b980a3eea0..00000000000
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-using System;
-using System.IO;
-using System.Windows;
-using ICSharpCode.SharpZipLib.Zip;
-using Newtonsoft.Json;
-using Flow.Launcher.Plugin;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Logger;
-
-namespace Flow.Launcher.Core.Plugin
-{
- internal class PluginInstaller
- {
- internal static void Install(string path)
- {
- if (File.Exists(path))
- {
- string tempFolder = Path.Combine(Path.GetTempPath(), "flowlauncher", "plugins");
- if (Directory.Exists(tempFolder))
- {
- Directory.Delete(tempFolder, true);
- }
- UnZip(path, tempFolder, true);
-
- string jsonPath = Path.Combine(tempFolder, Constant.PluginMetadataFileName);
- if (!File.Exists(jsonPath))
- {
- MessageBox.Show("Install failed: plugin config is missing");
- return;
- }
-
- PluginMetadata plugin = GetMetadataFromJson(tempFolder);
- if (plugin == null || plugin.Name == null)
- {
- MessageBox.Show("Install failed: plugin config is invalid");
- return;
- }
-
- string pluginFolderPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory;
-
- string newPluginName = plugin.Name
- .Replace("/", "_")
- .Replace("\\", "_")
- .Replace(":", "_")
- .Replace("<", "_")
- .Replace(">", "_")
- .Replace("?", "_")
- .Replace("*", "_")
- .Replace("|", "_")
- + "-" + Guid.NewGuid();
-
- string newPluginPath = Path.Combine(pluginFolderPath, newPluginName);
-
- string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" +
- $"Name: {plugin.Name}{Environment.NewLine}" +
- $"Version: {plugin.Version}{Environment.NewLine}" +
- $"Author: {plugin.Author}";
- PluginPair existingPlugin = PluginManager.GetPluginForId(plugin.ID);
-
- if (existingPlugin != null)
- {
- content = $"Do you want to update following plugin?{Environment.NewLine}{Environment.NewLine}" +
- $"Name: {plugin.Name}{Environment.NewLine}" +
- $"Old Version: {existingPlugin.Metadata.Version}" +
- $"{Environment.NewLine}New Version: {plugin.Version}" +
- $"{Environment.NewLine}Author: {plugin.Author}";
- }
-
- var result = MessageBox.Show(content, "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (result == MessageBoxResult.Yes)
- {
- if (existingPlugin != null && Directory.Exists(existingPlugin.Metadata.PluginDirectory))
- {
- //when plugin is in use, we can't delete them. That's why we need to make plugin folder a random name
- File.Create(Path.Combine(existingPlugin.Metadata.PluginDirectory, "NeedDelete.txt")).Close();
- }
-
- Directory.Move(tempFolder, newPluginPath);
-
- //exsiting plugins may be has loaded by application,
- //if we try to delelte those kind of plugins, we will get a error that indicate the
- //file is been used now.
- //current solution is to restart Flow Launcher. Ugly.
- //if (MainWindow.Initialized)
- //{
- // Plugins.Initialize();
- //}
- if (MessageBox.Show($"You have installed plugin {plugin.Name} successfully.{Environment.NewLine}" +
- "Restart Flow Launcher to take effect?",
- "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
- {
- PluginManager.API.RestartApp();
- }
- }
- }
- }
-
- private static PluginMetadata GetMetadataFromJson(string pluginDirectory)
- {
- string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
- PluginMetadata metadata;
-
- if (!File.Exists(configPath))
- {
- return null;
- }
-
- try
- {
- metadata = JsonConvert.DeserializeObject(File.ReadAllText(configPath));
- metadata.PluginDirectory = pluginDirectory;
- }
- catch (Exception e)
- {
- Log.Exception($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid json format", e);
- return null;
- }
-
- if (!AllowedLanguage.IsAllowed(metadata.Language))
- {
- Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid language {metadata.Language}");
- return null;
- }
- if (!File.Exists(metadata.ExecuteFilePath))
- {
- Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: file {metadata.ExecuteFilePath} doesn't exist");
- return null;
- }
-
- return metadata;
- }
-
- ///
- /// unzip plugin contents to the given directory.
- ///
- /// The path to the zip file.
- /// The output directory.
- /// overwirte
- private static void UnZip(string zipFile, string strDirectory, bool overWrite)
- {
- if (strDirectory == "")
- strDirectory = Directory.GetCurrentDirectory();
-
- using (ZipInputStream zipStream = new ZipInputStream(File.OpenRead(zipFile)))
- {
- ZipEntry theEntry;
-
- while ((theEntry = zipStream.GetNextEntry()) != null)
- {
- var pathToZip = theEntry.Name;
- var directoryName = String.IsNullOrEmpty(pathToZip) ? "" : Path.GetDirectoryName(pathToZip);
- var fileName = Path.GetFileName(pathToZip);
- var destinationDir = Path.Combine(strDirectory, directoryName);
- var destinationFile = Path.Combine(destinationDir, fileName);
-
- Directory.CreateDirectory(destinationDir);
-
- if (String.IsNullOrEmpty(fileName) || (File.Exists(destinationFile) && !overWrite))
- continue;
-
- using (FileStream streamWriter = File.Create(destinationFile))
- {
- zipStream.CopyTo(streamWriter);
- }
- }
- }
- }
- }
-}
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 5cde9de83a8..3b697a1ee6c 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -133,11 +133,6 @@ public static void InitializePlugins(IPublicAPI api)
}
}
- public static void InstallPlugin(string path)
- {
- PluginInstaller.Install(path);
- }
-
public static List ValidPluginsForQuery(Query query)
{
if (NonGlobalPlugins.ContainsKey(query.ActionKeyword))
diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs
index b7d274205ad..1d5f240e1be 100644
--- a/Flow.Launcher.Infrastructure/Http/Http.cs
+++ b/Flow.Launcher.Infrastructure/Http/Http.cs
@@ -66,18 +66,12 @@ public static async Task Get([NotNull] string url, string encoding = "UT
response = response.NonNull();
var stream = response.GetResponseStream().NonNull();
- using (var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)))
- {
- var content = await reader.ReadToEndAsync();
- if (response.StatusCode == HttpStatusCode.OK)
- {
- return content;
- }
- else
- {
- throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
- }
- }
+ using var reader = new StreamReader(stream, Encoding.GetEncoding(encoding));
+ var content = await reader.ReadToEndAsync();
+ if (response.StatusCode != HttpStatusCode.OK)
+ throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
+
+ return content;
}
}
}
\ No newline at end of file
diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs
index 68197390509..ccc00d5e938 100644
--- a/Flow.Launcher.Plugin/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/IPublicAPI.cs
@@ -63,12 +63,6 @@ public interface IPublicAPI
///
void OpenSettingDialog();
- ///
- /// Install Flow Launcher plugin
- ///
- /// Plugin path (ends with .flowlauncher)
- void InstallPlugin(string path);
-
///
/// Get translation of current language
/// You need to implement IPluginI18n if you want to support multiple languages for your plugin
diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln
index 6196aa5df1f..4d8997177ff 100644
--- a/Flow.Launcher.sln
+++ b/Flow.Launcher.sln
@@ -15,6 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc
ProjectSection(ProjectDependencies) = postProject
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {1EE20B48-82FB-48A2-8086-675D6DDAB4F0}
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {0B9DE348-9361-4940-ADB6-F5953BFFCCEC}
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217} = {4792A74A-0CEA-4173-A8B2-30E6764C6217}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
@@ -23,15 +24,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launc
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37}
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26}
- {049490F0-ECD2-4148-9B39-2135EC346EBE} = {049490F0-ECD2-4148-9B39-2135EC346EBE}
{403B57F2-1856-4FC7-8A24-36AB346B763E} = {403B57F2-1856-4FC7-8A24-36AB346B763E}
{588088F4-3262-4F9F-9663-A05DE12534C3} = {588088F4-3262-4F9F-9663-A05DE12534C3}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Infrastructure", "Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj", "{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginManagement", "Plugins\Flow.Launcher.Plugin.PluginManagement\Flow.Launcher.Plugin.PluginManagement.csproj", "{049490F0-ECD2-4148-9B39-2135EC346EBE}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Core", "Flow.Launcher.Core\Flow.Launcher.Core.csproj", "{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Program", "Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj", "{FDB3555B-58EF-4AE6-B5F1-904719637AB4}"
@@ -71,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.ProcessKiller", "Plugins\Flow.Launcher.Plugin.ProcessKiller\Flow.Launcher.Plugin.ProcessKiller.csproj", "{588088F4-3262-4F9F-9663-A05DE12534C3}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.PluginsManager", "Plugins\Flow.Launcher.Plugin.PluginsManager\Flow.Launcher.Plugin.PluginsManager.csproj", "{4792A74A-0CEA-4173-A8B2-30E6764C6217}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -129,18 +129,6 @@ Global
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.Build.0 = Release|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.ActiveCfg = Release|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x86.Build.0 = Release|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x64.ActiveCfg = Debug|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x64.Build.0 = Debug|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x86.ActiveCfg = Debug|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Debug|x86.Build.0 = Debug|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|Any CPU.Build.0 = Release|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x64.ActiveCfg = Release|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x64.Build.0 = Release|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x86.ActiveCfg = Release|Any CPU
- {049490F0-ECD2-4148-9B39-2135EC346EBE}.Release|x86.Build.0 = Release|Any CPU
{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -298,12 +286,23 @@ Global
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x64.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.Build.0 = Release|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x64.Build.0 = Debug|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Debug|x86.Build.0 = Debug|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x64.ActiveCfg = Release|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x64.Build.0 = Release|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x86.ActiveCfg = Release|Any CPU
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {049490F0-ECD2-4148-9B39-2135EC346EBE} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{403B57F2-1856-4FC7-8A24-36AB346B763E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{1EE20B48-82FB-48A2-8086-675D6DDAB4F0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
@@ -316,6 +315,7 @@ Global
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{588088F4-3262-4F9F-9663-A05DE12534C3} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
+ {4792A74A-0CEA-4173-A8B2-30E6764C6217} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED}
diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml
index 0cc671ef65a..07bb9633903 100644
--- a/Flow.Launcher/MainWindow.xaml
+++ b/Flow.Launcher/MainWindow.xaml
@@ -22,7 +22,6 @@
Loaded="OnLoaded"
Initialized="OnInitialized"
Closing="OnClosing"
- Drop="OnDrop"
LocationChanged="OnLocationChanged"
Deactivated="OnDeactivated"
PreviewKeyDown="OnKeyDown"
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index 993eb76c50b..3812b4e1f0a 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -199,24 +199,6 @@ private void OnPreviewMouseButtonDown(object sender, MouseButtonEventArgs e)
}
}
- private void OnDrop(object sender, DragEventArgs e)
- {
- if (e.Data.GetDataPresent(DataFormats.FileDrop))
- {
- // Note that you can have more than one file.
- string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
- if (files[0].ToLower().EndsWith(".flowlauncher"))
- {
- PluginManager.InstallPlugin(files[0]);
- }
- else
- {
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidFlowLauncherPluginFileFormat"));
- }
- }
- e.Handled = false;
- }
-
private void OnPreviewDragOver(object sender, DragEventArgs e)
{
e.Handled = true;
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 0cc5a0e5d1f..90d4fff63e8 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -115,11 +115,6 @@ public void StopLoadingBar()
_mainVM.ProgressBarVisibility = Visibility.Collapsed;
}
- public void InstallPlugin(string path)
- {
- Application.Current.Dispatcher.Invoke(() => PluginManager.InstallPlugin(path));
- }
-
public string GetTranslation(string key)
{
return InternationalizationManager.Instance.GetTranslation(key);
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj
deleted file mode 100644
index 08e89d86125..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.Plugin.PluginManagement.csproj
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
- Library
- netcoreapp3.1
- {049490F0-ECD2-4148-9B39-2135EC346EBE}
- Properties
- Flow.Launcher.Plugin.PluginManagement
- Flow.Launcher.Plugin.PluginManagement
- true
- true
- false
- false
-
-
-
- true
- full
- false
- ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.PluginManagement\
- DEBUG;TRACE
- prompt
- 4
- false
-
-
-
- pdbonly
- true
- ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.PluginManagement\
- TRACE
- prompt
- 4
- false
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
-
- PreserveNewest
-
-
-
-
-
- MSBuild:Compile
- Designer
- PreserveNewest
-
-
-
-
-
- MSBuild:Compile
- Designer
- PreserveNewest
-
-
-
-
-
- MSBuild:Compile
- Designer
- PreserveNewest
-
-
-
-
-
- MSBuild:Compile
- Designer
- PreserveNewest
-
-
-
-
-
- MSBuild:Compile
- Designer
- PreserveNewest
-
-
-
-
-
- MSBuild:Compile
- Designer
- PreserveNewest
-
-
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.PluginResult.cs b/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.PluginResult.cs
deleted file mode 100644
index 7f5d75d4e98..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Flow.Launcher.PluginResult.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Flow.Launcher.Plugin.PluginManagement
-{
- public class FlowLauncherPluginResult
- {
- public string plugin_file;
- public string description;
- public int liked_count;
- public string name;
- public string version;
- }
-}
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Images/plugin.png b/Plugins/Flow.Launcher.Plugin.PluginManagement/Images/plugin.png
deleted file mode 100644
index 6ff9b8b1576..00000000000
Binary files a/Plugins/Flow.Launcher.Plugin.PluginManagement/Images/plugin.png and /dev/null differ
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/de.xaml
deleted file mode 100644
index 38b2f1b4b3a..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/de.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Flow Launcher Plugin Verwaltung
- Installiere/Entferne/Aktualisiere Flow Launcher Plugins
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/en.xaml
deleted file mode 100644
index b49f33c767e..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/en.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Plugin Management
- Install, remove or update Flow Launcher plugins
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/pl.xaml
deleted file mode 100644
index 362db73e5ed..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/pl.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Zarządzanie wtyczkami Flow Launcher
- Pozwala na instalacje, usuwanie i aktualizacje wtyczek
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/sk.xaml
deleted file mode 100644
index b51eceb6aef..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/sk.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Správca pluginov
- Inštalácia, odinštalácia alebo aktualizácia pluginov Flow Launchera
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/tr.xaml
deleted file mode 100644
index fee82a78bcb..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/tr.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Flow Launcher Eklenti Yöneticisi
- Flow Launcher eklentilerini kurun, kaldırın ya da güncelleyin
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-cn.xaml
deleted file mode 100644
index 009fd976c19..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-cn.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Flow Launcher插件管理
- 安装/卸载/更新Flow Launcher插件
-
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-tw.xaml
deleted file mode 100644
index c93d740f133..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Languages/zh-tw.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Flow Launcher 外掛管理
- 安裝/解除安裝/更新 Flow Launcher 外掛
-
-
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs
deleted file mode 100644
index e1b631517e2..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/Main.cs
+++ /dev/null
@@ -1,258 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using Newtonsoft.Json;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Http;
-using Flow.Launcher.Infrastructure.Logger;
-
-namespace Flow.Launcher.Plugin.PluginManagement
-{
- public class Main : IPlugin, IPluginI18n
- {
- private static string APIBASE = "http://api.wox.one";
- private static string pluginSearchUrl = APIBASE + "/plugin/search/";
- private const string ListCommand = "list";
- private const string InstallCommand = "install";
- private const string UninstallCommand = "uninstall";
- private PluginInitContext context;
-
- public List Query(Query query)
- {
- List results = new List();
-
- if (string.IsNullOrEmpty(query.Search))
- {
- results.Add(ResultForListCommandAutoComplete(query));
- results.Add(ResultForInstallCommandAutoComplete(query));
- results.Add(ResultForUninstallCommandAutoComplete(query));
- return results;
- }
-
- string command = query.FirstSearch.ToLower();
- if (string.IsNullOrEmpty(command)) return results;
-
- if (command == ListCommand)
- {
- return ResultForListInstalledPlugins();
- }
- if (command == UninstallCommand)
- {
- return ResultForUnInstallPlugin(query);
- }
- if (command == InstallCommand)
- {
- return ResultForInstallPlugin(query);
- }
-
- if (InstallCommand.Contains(command))
- {
- results.Add(ResultForInstallCommandAutoComplete(query));
- }
- if (UninstallCommand.Contains(command))
- {
- results.Add(ResultForUninstallCommandAutoComplete(query));
- }
- if (ListCommand.Contains(command))
- {
- results.Add(ResultForListCommandAutoComplete(query));
- }
-
- return results;
- }
-
- private Result ResultForListCommandAutoComplete(Query query)
- {
- string title = ListCommand;
- string subtitle = "list installed plugins";
- return ResultForCommand(query, ListCommand, title, subtitle);
- }
-
- private Result ResultForInstallCommandAutoComplete(Query query)
- {
- string title = $"{InstallCommand} ";
- string subtitle = "list installed plugins";
- return ResultForCommand(query, InstallCommand, title, subtitle);
- }
-
- private Result ResultForUninstallCommandAutoComplete(Query query)
- {
- string title = $"{UninstallCommand} ";
- string subtitle = "list installed plugins";
- return ResultForCommand(query, UninstallCommand, title, subtitle);
- }
-
- private Result ResultForCommand(Query query, string command, string title, string subtitle)
- {
- const string seperater = Plugin.Query.TermSeperater;
- var result = new Result
- {
- Title = title,
- IcoPath = "Images\\plugin.png",
- SubTitle = subtitle,
- Action = e =>
- {
- context.API.ChangeQuery($"{query.ActionKeyword}{seperater}{command}{seperater}");
- return false;
- }
- };
- return result;
- }
-
- private List ResultForInstallPlugin(Query query)
- {
- List results = new List();
- string pluginName = query.SecondSearch;
- if (string.IsNullOrEmpty(pluginName)) return results;
- string json;
- try
- {
- json = Http.Get(pluginSearchUrl + pluginName).Result;
- }
- catch (WebException e)
- {
- //todo happlebao add option in log to decide give user prompt or not
- context.API.ShowMsg("PluginManagement.ResultForInstallPlugin: Can't connect to Wox plugin website, check your conenction");
- Log.Exception("|PluginManagement.ResultForInstallPlugin|Can't connect to Wox plugin website, check your conenction", e);
- return new List();
- }
- List searchedPlugins;
- try
- {
- searchedPlugins = JsonConvert.DeserializeObject>(json);
- }
- catch (JsonSerializationException e)
- {
- context.API.ShowMsg("PluginManagement.ResultForInstallPlugin: Coundn't parse api search results, Please update your Flow Launcher!");
- Log.Exception("|PluginManagement.ResultForInstallPlugin|Coundn't parse api search results, Please update your Flow Launcher!", e);
- return results;
- }
-
- foreach (FlowLauncherPluginResult r in searchedPlugins)
- {
- FlowLauncherPluginResult r1 = r;
- results.Add(new Result
- {
- Title = r.name,
- SubTitle = r.description,
- IcoPath = "Images\\plugin.png",
- TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.name).MatchData,
- SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.description).MatchData,
- Action = c =>
- {
- MessageBoxResult result = MessageBox.Show("Are you sure you wish to install the \'" + r.name + "\' plugin",
- "Install plugin", MessageBoxButton.YesNo);
-
- if (result == MessageBoxResult.Yes)
- {
- string folder = Path.Combine(Path.GetTempPath(), "FlowLauncherPluginDownload");
- if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
- string filePath = Path.Combine(folder, Guid.NewGuid().ToString() + ".flowlauncher");
-
- string pluginUrl = APIBASE + "/media/" + r1.plugin_file;
-
- try
- {
- Http.Download(pluginUrl, filePath);
- }
- catch (WebException e)
- {
- context.API.ShowMsg($"PluginManagement.ResultForInstallPlugin: download failed for <{r.name}>");
- Log.Exception($"|PluginManagement.ResultForInstallPlugin|download failed for <{r.name}>", e);
- return false;
- }
- context.API.InstallPlugin(filePath);
- }
- return false;
- }
- });
- }
- return results;
- }
-
- private List ResultForUnInstallPlugin(Query query)
- {
- List results = new List();
- List allInstalledPlugins = context.API.GetAllPlugins().Select(o => o.Metadata).ToList();
- if (!string.IsNullOrEmpty(query.SecondSearch))
- {
- allInstalledPlugins =
- allInstalledPlugins.Where(o => o.Name.ToLower().Contains(query.SecondSearch.ToLower())).ToList();
- }
-
- foreach (PluginMetadata plugin in allInstalledPlugins)
- {
- results.Add(new Result
- {
- Title = plugin.Name,
- SubTitle = plugin.Description,
- IcoPath = plugin.IcoPath,
- TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Name).MatchData,
- SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Description).MatchData,
- Action = e =>
- {
- UnInstallPlugin(plugin);
- return false;
- }
- });
- }
- return results;
- }
-
- private void UnInstallPlugin(PluginMetadata plugin)
- {
- string content = $"Do you want to uninstall following plugin?{Environment.NewLine}{Environment.NewLine}" +
- $"Name: {plugin.Name}{Environment.NewLine}" +
- $"Version: {plugin.Version}{Environment.NewLine}" +
- $"Author: {plugin.Author}";
- if (MessageBox.Show(content, "Flow Launcher", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
- {
- File.Create(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")).Close();
- var result = MessageBox.Show($"You have uninstalled plugin {plugin.Name} successfully.{Environment.NewLine}" +
- "Restart Flow Launcher to take effect?",
- "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (result == MessageBoxResult.Yes)
- {
- context.API.RestartApp();
- }
- }
- }
-
- private List ResultForListInstalledPlugins()
- {
- List results = new List();
- foreach (PluginMetadata plugin in context.API.GetAllPlugins().Select(o => o.Metadata))
- {
- string actionKeywordString = string.Join(" or ", plugin.ActionKeywords.ToArray());
- results.Add(new Result
- {
- Title = $"{plugin.Name} - Action Keywords: {actionKeywordString}",
- SubTitle = plugin.Description,
- IcoPath = plugin.IcoPath
- });
- }
- return results;
- }
-
- public void Init(PluginInitContext context)
- {
- this.context = context;
- }
-
- public string GetTranslatedPluginTitle()
- {
- return context.API.GetTranslation("flowlauncher_plugin_plugin_management_plugin_name");
- }
-
- public string GetTranslatedPluginDescription()
- {
- return context.API.GetTranslation("flowlauncher_plugin_plugin_management_plugin_description");
- }
- }
-}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginManagement/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginManagement/plugin.json
deleted file mode 100644
index 7b8262f795a..00000000000
--- a/Plugins/Flow.Launcher.Plugin.PluginManagement/plugin.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "ID": "D2D2C23B084D422DB66FE0C79D6C2A6A",
- "ActionKeyword": "wpm",
- "Name": "Plugin Management",
- "Description": "Install/Remove/Update Flow Launcher plugins",
- "Author": "qianlifeng",
- "Version": "1.1.1",
- "Language": "csharp",
- "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
- "ExecuteFileName": "Flow.Launcher.Plugin.PluginManagement.dll",
- "IcoPath": "Images\\plugin.png"
-}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
new file mode 100644
index 00000000000..d26c1ead46a
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
@@ -0,0 +1,29 @@
+using Flow.Launcher.Infrastructure.UserSettings;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.PluginsManager
+{
+ internal class ContextMenu : IContextMenu
+ {
+ private PluginInitContext Context { get; set; }
+
+ private Settings Settings { get; set; }
+
+ public ContextMenu(PluginInitContext context, Settings settings)
+ {
+ Context = context;
+ Settings = settings;
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ // Open website
+ // Go to source code
+ // Report an issue?
+ // Request a feature?
+ return new List();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
new file mode 100644
index 00000000000..cc1a931ce04
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
@@ -0,0 +1,46 @@
+
+
+
+ Library
+ netcoreapp3.1
+ true
+ true
+ true
+ false
+
+
+
+ ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.PluginsManager
+
+
+
+ ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.PluginsManager
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/pluginsmanager.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/pluginsmanager.png
new file mode 100644
index 00000000000..65f0e41dc14
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/pluginsmanager.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
new file mode 100644
index 00000000000..fc13f0c2fe7
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -0,0 +1,24 @@
+
+
+
+ Downloading plugin
+ Please wait...
+ Successfully downloaded
+ Do you want to uninstall the following plugin?{0}{1}{2} by {3}
+ Do you want to install the following plugin?{0}{1}{2} by {3}
+ Plugin Install
+ Plugin Uninstall
+ Install failed: unable to find the plugin.json metadata file from the new plugin
+ You have installed plugin {0} successfully.{1}Would you like to restart Flow Launcher to take effect?
+ You have uninstalled plugin {0} successfully.{1}Would you like to restart Flow Launcher to take effect?
+
+
+
+ Plugins Manager
+ Management of installing, uninstalling or updating Flow Launcher plugins
+
+
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs
new file mode 100644
index 00000000000..58ec5005fb5
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs
@@ -0,0 +1,66 @@
+using Flow.Launcher.Infrastructure.Storage;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin.PluginsManager.ViewModels;
+using Flow.Launcher.Plugin.PluginsManager.Views;
+using System.Collections.Generic;
+using System.Windows.Controls;
+
+namespace Flow.Launcher.Plugin.PluginsManager
+{
+ public class Main : ISettingProvider, IPlugin, ISavable, IContextMenu, IPluginI18n
+ {
+ internal PluginInitContext Context { get; set; }
+
+ internal Settings Settings;
+
+ private SettingsViewModel viewModel;
+
+ private IContextMenu contextMenu;
+
+ public Control CreateSettingPanel()
+ {
+ return new PluginsManagerSettings(viewModel);
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ Context = context;
+ viewModel = new SettingsViewModel(context);
+ Settings = viewModel.Settings;
+ contextMenu = new ContextMenu(Context, Settings);
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ return contextMenu.LoadContextMenus(selectedResult);
+ }
+
+ public List Query(Query query)
+ {
+ var search = query.Search.ToLower();
+
+ var pluginManager = new PluginsManager(Context, Settings);
+
+ if (!string.IsNullOrEmpty(search)
+ && ($"{Settings.UninstallHotkey} ".StartsWith(search) || search.StartsWith($"{Settings.UninstallHotkey} ")))
+ return pluginManager.RequestUninstall(search);
+
+ return pluginManager.RequestInstallOrUpdate(search);
+ }
+
+ public void Save()
+ {
+ viewModel.Save();
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return Context.API.GetTranslation("plugin_pluginsmanager_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description");
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs
new file mode 100644
index 00000000000..29022171007
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/PluginsManifest.cs
@@ -0,0 +1,40 @@
+using Flow.Launcher.Infrastructure.Http;
+using Flow.Launcher.Infrastructure.Logger;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Plugin.PluginsManager.Models
+{
+ internal class PluginsManifest
+ {
+ internal List UserPlugins { get; private set; }
+ internal PluginsManifest()
+ {
+ DownloadManifest();
+ }
+
+ private void DownloadManifest()
+ {
+ var json = string.Empty;
+ try
+ {
+ var t = Task.Run(
+ async () =>
+ json = await Http.Get("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json"));
+
+ t.Wait();
+
+ UserPlugins = JsonConvert.DeserializeObject>(json);
+ }
+ catch (Exception e)
+ {
+ Log.Exception("|PluginManagement.GetManifest|Encountered error trying to download plugins manifest", e);
+
+ UserPlugins = new List();
+ }
+
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs
new file mode 100644
index 00000000000..3bc44e0f6fe
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.PluginsManager.Models
+{
+ public class UserPlugin
+ {
+ public string ID { get; set; }
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public string Author { get; set; }
+ public string Version { get; set; }
+ public string Language { get; set; }
+ public string Website { get; set; }
+ public string UrlDownload { get; set; }
+ public string UrlSourceCode { get; set; }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
new file mode 100644
index 00000000000..fc4abc800b4
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -0,0 +1,235 @@
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.Http;
+using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin.PluginsManager.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
+
+namespace Flow.Launcher.Plugin.PluginsManager
+{
+ internal class PluginsManager
+ {
+ private readonly PluginsManifest pluginsManifest;
+
+ private PluginInitContext Context { get; set; }
+
+ private Settings Settings { get; set; }
+
+ private readonly string icoPath = "Images\\pluginsmanager.png";
+
+ internal PluginsManager(PluginInitContext context, Settings settings)
+ {
+ pluginsManifest = new PluginsManifest();
+ Context = context;
+ Settings = settings;
+ }
+ internal void InstallOrUpdate(UserPlugin plugin)
+ {
+ if (PluginExists(plugin.ID))
+ {
+ Context.API.ShowMsg("Plugin already installed");
+ return;
+ }
+
+ var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
+ Environment.NewLine, Environment.NewLine,
+ plugin.Name, plugin.Author);
+
+ if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No)
+ return;
+
+ var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}.zip");
+
+ try
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
+ Context.API.GetTranslation("plugin_pluginsmanager_please_wait"));
+
+ Http.Download(plugin.UrlDownload, filePath);
+
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
+ Context.API.GetTranslation("plugin_pluginsmanager_download_success"));
+ }
+ catch (Exception e)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
+ Context.API.GetTranslation("plugin_pluginsmanager_download_success"));
+
+ Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload");
+ }
+
+ Application.Current.Dispatcher.Invoke(() => Install(plugin, filePath));
+ }
+
+ internal void Update()
+ {
+ throw new NotImplementedException();
+ }
+
+ internal bool PluginExists(string id)
+ {
+ return Context.API.GetAllPlugins().Any(x => x.Metadata.ID == id);
+ }
+
+ internal void PluginsManifestSiteOpen()
+ {
+ //Open from context menu https://git.vcmq.workers.dev/Flow-Launcher/Flow.Launcher.PluginsManifest
+ throw new NotImplementedException();
+ }
+
+ internal List Search(List results, string searchName)
+ {
+ if (string.IsNullOrEmpty(searchName))
+ return results;
+
+ return results
+ .Where(x =>
+ {
+ var matchResult = StringMatcher.FuzzySearch(searchName, x.Title);
+ if (matchResult.IsSearchPrecisionScoreMet())
+ x.Score = matchResult.Score;
+
+ return matchResult.IsSearchPrecisionScoreMet();
+ })
+ .ToList();
+ }
+
+ internal List RequestInstallOrUpdate(string searchName)
+ {
+ var results =
+ pluginsManifest
+ .UserPlugins
+ .Select(x =>
+ new Result
+ {
+ Title = $"{x.Name} by {x.Author}",
+ SubTitle = x.Description,
+ IcoPath = icoPath,
+ Action = e =>
+ {
+ Application.Current.MainWindow.Hide();
+ InstallOrUpdate(x);
+
+ return true;
+ }
+ })
+ .ToList();
+
+ return Search(results, searchName);
+ }
+
+ private void Install(UserPlugin plugin, string downloadedFilePath)
+ {
+ if (!File.Exists(downloadedFilePath))
+ return;
+
+ var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
+ var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
+
+ if (Directory.Exists(tempFolderPath))
+ Directory.Delete(tempFolderPath, true);
+
+ Directory.CreateDirectory(tempFolderPath);
+
+ var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
+
+ File.Move(downloadedFilePath, zipFilePath);
+
+ Utilities.UnZip(zipFilePath, tempFolderPluginPath, true);
+
+ var pluginFolderPath = Utilities.GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
+
+ var metadataJsonFilePath = string.Empty;
+ if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
+ metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
+
+ if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
+ {
+ MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
+ return;
+ }
+
+ string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}");
+
+ Directory.Move(pluginFolderPath, newPluginPath);
+
+ if (MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_successandrestart"),
+ plugin.Name, Environment.NewLine),
+ Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
+ MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
+ Context.API.RestartApp();
+ }
+
+ internal List RequestUninstall(string search)
+ {
+ if (!string.IsNullOrEmpty(search)
+ && Settings.UninstallHotkey.StartsWith(search)
+ && (Settings.UninstallHotkey != search || !search.StartsWith(Settings.UninstallHotkey)))
+ {
+ return
+ new List
+ {
+ new Result
+ {
+ Title = "Uninstall",
+ IcoPath = icoPath,
+ SubTitle = "Select a plugin to uninstall",
+ Action = e =>
+ {
+ Context
+ .API
+ .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UninstallHotkey} ");
+
+ return false;
+ }
+ }
+ };
+ }
+
+ var uninstallSearch = search.Replace(Settings.UninstallHotkey, string.Empty).TrimStart();
+
+ var results= Context.API
+ .GetAllPlugins()
+ .Select(x =>
+ new Result
+ {
+ Title = $"{x.Metadata.Name} by {x.Metadata.Author}",
+ SubTitle = x.Metadata.Description,
+ IcoPath = x.Metadata.IcoPath,
+ Action = e =>
+ {
+ Application.Current.MainWindow.Hide();
+ Uninstall(x.Metadata);
+
+ return true;
+ }
+ })
+ .ToList();
+
+ return Search(results, uninstallSearch);
+ }
+
+ private void Uninstall(PluginMetadata plugin)
+ {
+ string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
+ Environment.NewLine, Environment.NewLine,
+ plugin.Name, plugin.Author);
+
+ if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
+ MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
+
+ if (MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_successandrestart"),
+ plugin.Name, Environment.NewLine),
+ Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
+ MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
+ Context.API.RestartApp();
+ }
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
new file mode 100644
index 00000000000..0c647e6ae96
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Flow.Launcher.Plugin.PluginsManager
+{
+ internal class Settings
+ {
+ internal string UninstallHotkey { get; set; } = "uninstall";
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
new file mode 100644
index 00000000000..2853ffc9e28
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
@@ -0,0 +1,61 @@
+using Flow.Launcher.Infrastructure.Http;
+using ICSharpCode.SharpZipLib.Zip;
+using System.IO;
+using System.Net;
+
+namespace Flow.Launcher.Plugin.PluginsManager
+{
+ internal static class Utilities
+ {
+ ///
+ /// Unzip contents to the given directory.
+ ///
+ /// The path to the zip file.
+ /// The output directory.
+ /// overwrite
+ internal static void UnZip(string zipFilePath, string strDirectory, bool overwrite)
+ {
+ if (strDirectory == "")
+ strDirectory = Directory.GetCurrentDirectory();
+
+ using var zipStream = new ZipInputStream(File.OpenRead(zipFilePath));
+
+ ZipEntry theEntry;
+
+ while ((theEntry = zipStream.GetNextEntry()) != null)
+ {
+ var pathToZip = theEntry.Name;
+ var directoryName = string.IsNullOrEmpty(pathToZip) ? "" : Path.GetDirectoryName(pathToZip);
+ var fileName = Path.GetFileName(pathToZip);
+ var destinationDir = Path.Combine(strDirectory, directoryName);
+ var destinationFile = Path.Combine(destinationDir, fileName);
+
+ Directory.CreateDirectory(destinationDir);
+
+ if (string.IsNullOrEmpty(fileName) || (File.Exists(destinationFile) && !overwrite))
+ continue;
+
+ using var streamWriter = File.Create(destinationFile);
+ zipStream.CopyTo(streamWriter);
+ }
+ }
+
+ internal static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath)
+ {
+ var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length;
+ var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length;
+
+ // adjust path depending on how the plugin is zipped up
+ // the recommended should be to zip up the folder not the contents
+ if (unzippedFolderCount == 1 && unzippedFilesCount == 0)
+ // folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/
+ return Directory.GetDirectories(unzippedParentFolderPath)[0];
+
+ if (unzippedFilesCount > 1)
+ // content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/
+ return unzippedParentFolderPath;
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
new file mode 100644
index 00000000000..f3cf117d33f
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
@@ -0,0 +1,26 @@
+using Flow.Launcher.Infrastructure.Storage;
+using Flow.Launcher.Infrastructure.UserSettings;
+
+namespace Flow.Launcher.Plugin.PluginsManager.ViewModels
+{
+ public class SettingsViewModel
+ {
+ private readonly PluginJsonStorage storage;
+
+ internal Settings Settings { get; set; }
+
+ internal PluginInitContext Context { get; set; }
+
+ public SettingsViewModel(PluginInitContext context)
+ {
+ Context = context;
+ storage = new PluginJsonStorage();
+ Settings = storage.Load();
+ }
+
+ public void Save()
+ {
+ storage.Save();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
new file mode 100644
index 00000000000..89d27f6ffea
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml.cs
new file mode 100644
index 00000000000..14204eda930
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml.cs
@@ -0,0 +1,22 @@
+
+using Flow.Launcher.Plugin.PluginsManager.ViewModels;
+
+namespace Flow.Launcher.Plugin.PluginsManager.Views
+{
+ ///
+ /// Interaction logic for PluginsManagerSettings.xaml
+ ///
+ public partial class PluginsManagerSettings
+ {
+ private readonly SettingsViewModel viewModel;
+
+ public PluginsManagerSettings(SettingsViewModel viewModel)
+ {
+ InitializeComponent();
+
+ this.viewModel = viewModel;
+
+ //RefreshView();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
new file mode 100644
index 00000000000..327011ac31d
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
@@ -0,0 +1,14 @@
+{
+ "ID": "9f8f9b14-2518-4907-b211-35ab6290dee7",
+ "ActionKeywords": [
+ "pm"
+ ],
+ "Name": "Plugins Manager",
+ "Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
+ "Author": "Jeremy Wu",
+ "Version": "1.0.0",
+ "Language": "csharp",
+ "Website": "https://github.com/Flow-Launcher/Flow.Launcher",
+ "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
+ "IcoPath": "Images\\pluginsmanager.png"
+}