diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index f8c9a3f17..47edc21c2 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -11,6 +11,8 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using ISavable = Flow.Launcher.Plugin.ISavable; +using Flow.Launcher.Plugin.SharedCommands; +using System.Text.Json; namespace Flow.Launcher.Core.Plugin { @@ -27,9 +29,9 @@ namespace Flow.Launcher.Core.Plugin public static IPublicAPI API { private set; get; } - // todo happlebao, this should not be public, the indicator function should be embeded - public static PluginsSettings Settings; + private static PluginsSettings Settings; private static List _metadatas; + private static List _modifiedPlugins = new List(); /// /// Directories that will hold Flow Launcher plugin directory @@ -331,5 +333,156 @@ namespace Flow.Launcher.Core.Plugin RemoveActionKeyword(id, oldActionKeyword); } } + + private 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; + } + + private static bool SameOrLesserPluginVersionExists(string metadataPath) + { + var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath)); + return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID + && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0); + } + + #region Public functions + + public static bool PluginModified(string uuid) + { + return _modifiedPlugins.Contains(uuid); + } + + + /// + /// Update a plugin to new version, from a zip file. Will Delete zip after updating. + /// + public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) + { + InstallPlugin(newVersion, zipFilePath, checkModified:false); + UninstallPlugin(existingVersion, removeSettings:false, checkModified:false); + _modifiedPlugins.Add(existingVersion.ID); + } + + /// + /// Install a plugin. Will Delete zip after updating. + /// + public static void InstallPlugin(UserPlugin plugin, string zipFilePath) + { + InstallPlugin(plugin, zipFilePath, true); + } + + /// + /// Uninstall a plugin. + /// + public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true) + { + UninstallPlugin(plugin, removeSettings, true); + } + + #endregion + + #region Internal functions + + internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified) + { + if (checkModified && PluginModified(plugin.ID)) + { + // Distinguish exception from installing same or less version + throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin)); + } + + // Unzip plugin files to temp folder + var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath); + File.Delete(zipFilePath); + + var pluginFolderPath = 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)) + { + throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist"); + } + + if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) + { + throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}"); + } + + var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; + + var defaultPluginIDs = new List + { + "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark + "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator + "572be03c74c642baae319fc283e561a8", // Explorer + "6A122269676E40EB86EB543B945932B9", // PluginIndicator + "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager + "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller + "791FC278BA414111B8D1886DFE447410", // Program + "D409510CD0D2481F853690A07E6DC426", // Shell + "CEA08895D2544B019B2E9C5009600DF4", // Sys + "0308FD86DE0A4DEE8D62B9B535370992", // URL + "565B73353DBF4806919830B9202EE3BF", // WebSearch + "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings + }; + + // Treat default plugin differently, it needs to be removable along with each flow release + var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) + ? DataLocation.PluginsDirectory + : Constant.PreinstalledDirectory; + + var newPluginPath = Path.Combine(installDirectory, folderName); + + FilesFolders.CopyAll(pluginFolderPath, newPluginPath); + + Directory.Delete(tempFolderPluginPath, true); + + if (checkModified) + { + _modifiedPlugins.Add(plugin.ID); + } + } + + internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified) + { + if (checkModified && PluginModified(plugin.ID)) + { + throw new ArgumentException($"Plugin {plugin.Name} has been modified"); + } + + if (removeSettings) + { + Settings.Plugins.Remove(plugin.ID); + AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + } + + // Marked for deletion. Will be deleted on next start up + using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); + + if (checkModified) + { + _modifiedPlugins.Add(plugin.ID); + } + } + + #endregion } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index 580954f3c..17e9fe2bc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -13,6 +13,10 @@ namespace Flow.Launcher.Plugin.PluginsManager Context = context; } + private readonly GlyphInfo sourcecodeGlyph = new("/Resources/#Segoe Fluent Icons","\uE943"); + private readonly GlyphInfo issueGlyph = new("/Resources/#Segoe Fluent Icons", "\ued15"); + private readonly GlyphInfo manifestGlyph = new("/Resources/#Segoe Fluent Icons", "\uea37"); + public List LoadContextMenus(Result selectedResult) { if(selectedResult.ContextData is not UserPlugin pluginManifestInfo) @@ -36,6 +40,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"), IcoPath = "Images\\sourcecode.png", + Glyph = sourcecodeGlyph, Action = _ => { Context.API.OpenUrl(pluginManifestInfo.UrlSourceCode); @@ -47,6 +52,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"), IcoPath = "Images\\request.png", + Glyph = issueGlyph, Action = _ => { // standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master @@ -63,6 +69,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"), IcoPath = "Images\\manifestsite.png", + Glyph = manifestGlyph, Action = _ => { Context.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"); diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 51882a20e..92500ae6a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -36,8 +36,4 @@ PreserveNewest - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index 1f74a49a2..42a1ac9b8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -8,7 +8,9 @@ Successfully downloaded {0} Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -18,19 +20,25 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling 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. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -48,4 +56,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 683904ea0..00f77f872 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -117,9 +116,19 @@ namespace Flow.Launcher.Plugin.PluginsManager return; } - var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), - plugin.Name, plugin.Author, - Environment.NewLine, Environment.NewLine); + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt_no_restart"), + plugin.Name, plugin.Author, + Environment.NewLine); + } if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) @@ -130,36 +139,48 @@ namespace Flow.Launcher.Plugin.PluginsManager ? $"{plugin.Name}-{Guid.NewGuid()}.zip" : $"{plugin.Name}-{plugin.Version}.zip"; - var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename); + var filePath = Path.Combine(Path.GetTempPath(), downloadFilename); try { - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), plugin.Name)); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); Install(plugin, filePath); } - catch (Exception e) + catch (HttpRequestException e) { - if (e is HttpRequestException) - MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"), - Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin")); - - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), - plugin.Name)); - - Log.Exception("PluginsManager", "An error occurred while downloading plugin", e, "InstallOrUpdate"); - + Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name), + Context.API.GetTranslation("plugin_pluginsmanager_download_error")); + Log.Exception("PluginsManager", "An error occurred while downloading plugin", e); return; } - - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), plugin.Name)); - - Context.API.RestartApp(); + catch (Exception e) + { + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), + plugin.Name)); + Log.Exception("PluginsManager", "An error occurred while downloading plugin", e); + return; + } + + if (Settings.AutoRestartAfterChanging) + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), + plugin.Name)); + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"), + plugin.Name)); + } } internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false) @@ -172,6 +193,7 @@ namespace Flow.Launcher.Plugin.PluginsManager on existingPlugin.Metadata.ID equals pluginFromManifest.ID where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < 0 // if current version precedes manifest version + && !PluginManager.PluginModified(existingPlugin.Metadata.ID) select new { @@ -204,36 +226,57 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = x.IcoPath, Action = e => { - string message = string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), - x.Name, x.Author, - Environment.NewLine, Environment.NewLine); + + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + x.Name, x.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"), + x.Name, x.Author, + Environment.NewLine); + } if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), MessageBoxButton.YesNo) == MessageBoxResult.Yes) { - Uninstall(x.PluginExistingMetadata, false); - - var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, + var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{x.Name}-{x.NewVersion}.zip"); - Task.Run(async delegate + _ = Task.Run(async delegate { + if (File.Exists(downloadToFilePath)) + { + File.Delete(downloadToFilePath); + } + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) .ConfigureAwait(false); - Context.API.ShowMsg( - Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), x.Name)); + PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); - Install(x.PluginNewUserPlugin, downloadToFilePath); - - Context.API.RestartApp(); + if (Settings.AutoRestartAfterChanging) + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"), + x.Name)); + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"), + x.Name)); + } }).ContinueWith(t => { Log.Exception("PluginsManager", $"Update failed for {x.Name}", - t.Exception.InnerException, "RequestUpdate"); + t.Exception.InnerException); Context.API.ShowMsg( Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format( @@ -348,12 +391,13 @@ namespace Flow.Launcher.Plugin.PluginsManager var results = PluginsManifest .UserPlugins + .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID)) .Select(x => new Result { Title = $"{x.Name} by {x.Author}", SubTitle = x.Description, - IcoPath = icoPath, + IcoPath = x.IcoPath, Action = e => { if (e.SpecialKeyState.CtrlPressed) @@ -375,78 +419,29 @@ namespace Flow.Launcher.Plugin.PluginsManager 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.Copy(downloadedFilePath, zipFilePath); - - File.Delete(downloadedFilePath); - - 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)) + throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath); + try { - MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"), - Context.API.GetTranslation("plugin_pluginsmanager_install_error_title")); - - throw new FileNotFoundException( - string.Format("Unable to find plugin.json from the extracted zip file, or this path {0} does not exist", pluginFolderPath)); + PluginManager.InstallPlugin(plugin, downloadedFilePath); + File.Delete(downloadedFilePath); } - - if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) + catch (FileNotFoundException e) { - MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name), - Context.API.GetTranslation("plugin_pluginsmanager_install_error_title")); - - throw new InvalidOperationException( - string.Format("A plugin with the same ID and version already exists, " + - "or the version is greater than this downloaded plugin {0}", - plugin.Name)); + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile")); + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + } + catch (InvalidOperationException e) + { + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name)); + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + } + catch (ArgumentException e) { + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name)); + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); } - - var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; - - var defaultPluginIDs = new List - { - "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark - "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator - "572be03c74c642baae319fc283e561a8", // Explorer - "6A122269676E40EB86EB543B945932B9", // PluginIndicator - "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager - "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller - "791FC278BA414111B8D1886DFE447410", // Program - "D409510CD0D2481F853690A07E6DC426", // Shell - "CEA08895D2544B019B2E9C5009600DF4", // Sys - "0308FD86DE0A4DEE8D62B9B535370992", // URL - "565B73353DBF4806919830B9202EE3BF", // WebSearch - "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings - }; - - // Treat default plugin differently, it needs to be removable along with each flow release - var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) - ? DataLocation.PluginsDirectory - : Constant.PreinstalledDirectory; - - var newPluginPath = Path.Combine(installDirectory, folderName); - - FilesFolders.CopyAll(pluginFolderPath, newPluginPath); - - Directory.Delete(pluginFolderPath, true); } internal List RequestUninstall(string search) @@ -461,10 +456,19 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = x.Metadata.IcoPath, Action = e => { - string message = string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), - x.Metadata.Name, x.Metadata.Author, - Environment.NewLine, Environment.NewLine); + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine); + } if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), @@ -472,7 +476,16 @@ namespace Flow.Launcher.Plugin.PluginsManager { Application.Current.MainWindow.Hide(); Uninstall(x.Metadata); - Context.API.RestartApp(); + if (Settings.AutoRestartAfterChanging) + { + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_success_no_restart"), + x.Metadata.Name)); + } return true; } @@ -484,24 +497,18 @@ namespace Flow.Launcher.Plugin.PluginsManager return Search(results, search); } - private void Uninstall(PluginMetadata plugin, bool removedSetting = true) + private void Uninstall(PluginMetadata plugin) { - if (removedSetting) + try { - PluginManager.Settings.Plugins.Remove(plugin.ID); - PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + PluginManager.UninstallPlugin(plugin, removeSettings:true); + } + catch (ArgumentException e) + { + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"), + Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error")); } - - // Marked for deletion. Will be deleted on next start up - using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); - } - - private bool SameOrLesserPluginVersionExists(string metadataPath) - { - var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath)); - return Context.API.GetAllPlugins() - .Any(x => x.Metadata.ID == newMetadata.ID - && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0); } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index aa35f02b5..f23ff71f0 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -9,5 +9,7 @@ internal const string UpdateCommand = "update"; public bool WarnFromUnknownSource { get; set; } = true; + + public bool AutoRestartAfterChanging { get; set; } = false; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs index 672884f80..1c71507fc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs @@ -17,5 +17,11 @@ get => Settings.WarnFromUnknownSource; set => Settings.WarnFromUnknownSource = value; } + + public bool AutoRestartAfterChanging + { + get => Settings.AutoRestartAfterChanging; + set => Settings.AutoRestartAfterChanging = value; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml index a62a032f7..18be0d2ca 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml @@ -12,10 +12,22 @@ + + + + +