diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index 25a32534b18..8d24c145c45 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -11,6 +11,13 @@ Plugin Install Plugin Uninstall Install failed: unable to find the plugin.json metadata file from the new plugin + No update available + All plugins are up to date + {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + Plugin Update + This plugin has an update, would you like to see it? + This plugin is already installed + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 58ec5005fb5..43f92e7b91f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -42,9 +42,13 @@ public List Query(Query query) var pluginManager = new PluginsManager(Context, Settings); if (!string.IsNullOrEmpty(search) - && ($"{Settings.UninstallHotkey} ".StartsWith(search) || search.StartsWith($"{Settings.UninstallHotkey} "))) + && ($"{Settings.HotkeyUninstall} ".StartsWith(search) || search.StartsWith($"{Settings.HotkeyUninstall} "))) return pluginManager.RequestUninstall(search); - + + if (!string.IsNullOrEmpty(search) + && ($"{Settings.HotkeyUpdate} ".StartsWith(search) || search.StartsWith($"{Settings.HotkeyUpdate} "))) + return pluginManager.RequestUpdate(search); + return pluginManager.RequestInstallOrUpdate(search); } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs index 3bc44e0f6fe..c1af3014bf9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Models/UserPlugin.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - + namespace Flow.Launcher.Plugin.PluginsManager.Models { public class UserPlugin diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 23e038d4bd5..90f3277fb33 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -19,6 +19,21 @@ internal class PluginsManager private Settings Settings { get; set; } + private bool shouldHideWindow = true; + private bool ShouldHideWindow + { + set { shouldHideWindow = value; } + get + { + var setValue = shouldHideWindow; + // Default value for hide main window is true. Revert after get call. + // This ensures when set by another method to false, it is only used once. + shouldHideWindow = true; + + return setValue; + } + } + private readonly string icoPath = "Images\\pluginsmanager.png"; internal PluginsManager(PluginInitContext context, Settings settings) @@ -31,7 +46,22 @@ internal void InstallOrUpdate(UserPlugin plugin) { if (PluginExists(plugin.ID)) { - Context.API.ShowMsg("Plugin already installed"); + if (Context.API.GetAllPlugins().Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version != plugin.Version)) + { + if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + Context + .API + .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.HotkeyUpdate} {plugin.Name}"); + + Application.Current.MainWindow.Show(); + shouldHideWindow = false; + + return; + } + + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_alreadyexists")); return; } @@ -42,7 +72,7 @@ internal void InstallOrUpdate(UserPlugin plugin) 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"); + var filePath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}.zip"); try { @@ -62,12 +92,81 @@ internal void InstallOrUpdate(UserPlugin plugin) Log.Exception("PluginsManager", "An error occured while downloading plugin", e, "PluginDownload"); } - Application.Current.Dispatcher.Invoke(() => Install(plugin, filePath)); + Application.Current.Dispatcher.Invoke(() => { Install(plugin, filePath); Context.API.RestartApp(); }); } - internal void Update() + internal List RequestUpdate(string search) { - throw new NotImplementedException(); + var autocompletedResults = AutoCompleteReturnAllResults(search, + Settings.HotkeyUpdate, + "Update", + "Select a plugin to update"); + + if (autocompletedResults.Any()) + return autocompletedResults; + + var uninstallSearch = search.Replace(Settings.HotkeyUpdate, string.Empty).TrimStart(); + + + var resultsForUpdate = + from existingPlugin in Context.API.GetAllPlugins() + join pluginFromManifest in pluginsManifest.UserPlugins + on existingPlugin.Metadata.ID equals pluginFromManifest.ID + where existingPlugin.Metadata.Version != pluginFromManifest.Version + select + new + { + pluginFromManifest.Name, + pluginFromManifest.Author, + CurrentVersion = existingPlugin.Metadata.Version, + NewVersion = pluginFromManifest.Version, + existingPlugin.Metadata.IcoPath, + PluginExistingMetadata = existingPlugin.Metadata, + PluginNewUserPlugin = pluginFromManifest + }; + + if (!resultsForUpdate.Any()) + return new List { + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), + IcoPath = icoPath + }}; + + + var results = resultsForUpdate + .Select(x => + new Result + { + Title = $"{x.Name} by {x.Author}", + SubTitle = $"Update from version {x.CurrentVersion} to {x.NewVersion}", + IcoPath = x.IcoPath, + Action = e => + { + string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + x.Name, x.Author, + Environment.NewLine, Environment.NewLine); + + if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Uninstall(x.PluginExistingMetadata); + + var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, $"{x.Name}-{x.NewVersion}.zip"); + Http.Download(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath); + Install(x.PluginNewUserPlugin, downloadToFilePath); + + Context.API.RestartApp(); + + return true; + } + + return false; + } + }); + + return Search(results, uninstallSearch); } internal bool PluginExists(string id) @@ -75,16 +174,10 @@ 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) + internal List Search(IEnumerable results, string searchName) { if (string.IsNullOrEmpty(searchName)) - return results; + return results.ToList(); return results .Where(x => @@ -114,11 +207,10 @@ internal List RequestInstallOrUpdate(string searchName) Application.Current.MainWindow.Hide(); InstallOrUpdate(x); - return true; + return ShouldHideWindow; }, ContextData = x - }) - .ToList(); + }); return Search(results, searchName); } @@ -154,42 +246,24 @@ private void Install(UserPlugin plugin, string downloadedFilePath) return; } - string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}{plugin.ID}"); + string newPluginPath = Path.Combine(DataLocation.PluginsDirectory, $"{plugin.Name}-{plugin.Version}"); Directory.Move(pluginFolderPath, newPluginPath); - - 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} "); + var autocompletedResults = AutoCompleteReturnAllResults(search, + Settings.HotkeyUninstall, + "Uninstall", + "Select a plugin to uninstall"); - return false; - } - } - }; - } + if (autocompletedResults.Any()) + return autocompletedResults; - var uninstallSearch = search.Replace(Settings.UninstallHotkey, string.Empty).TrimStart(); + var uninstallSearch = search.Replace(Settings.HotkeyUninstall, string.Empty).TrimStart(); - var results= Context.API + var results = Context.API .GetAllPlugins() .Select(x => new Result @@ -199,30 +273,60 @@ internal List RequestUninstall(string search) IcoPath = x.Metadata.IcoPath, Action = e => { - Application.Current.MainWindow.Hide(); - Uninstall(x.Metadata); + string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine, Environment.NewLine); - return true; + if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Application.Current.MainWindow.Hide(); + Uninstall(x.Metadata); + Context.API.RestartApp(); + + return true; + } + + return false; } - }) - .ToList(); + }); return Search(results, uninstallSearch); } private void Uninstall(PluginMetadata plugin) { - string message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), - plugin.Name, plugin.Author, - Environment.NewLine, Environment.NewLine); + // Marked for deletion. Will be deleted on next start up + using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); + } - if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) + private List AutoCompleteReturnAllResults(string search, string hotkey, string title, string subtitle) + { + if (!string.IsNullOrEmpty(search) + && hotkey.StartsWith(search) + && (hotkey != search || !search.StartsWith(hotkey))) { - using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); - - Context.API.RestartApp(); + return + new List + { + new Result + { + Title = title, + IcoPath = icoPath, + SubTitle = subtitle, + Action = e => + { + Context + .API + .ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {hotkey} "); + + return false; + } + } + }; } + + return new List(); } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index 0c647e6ae96..e2e8d22e59f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -6,6 +6,8 @@ namespace Flow.Launcher.Plugin.PluginsManager { internal class Settings { - internal string UninstallHotkey { get; set; } = "uninstall"; + internal string HotkeyUninstall { get; set; } = "uninstall"; + + internal string HotkeyUpdate { get; set; } = "update"; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 73e03d525b6..e970e5a8ecd 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "1.2.0", + "Version": "1.3.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",