diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs index f6e5e5879a0..ed8f94bcf25 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs @@ -44,8 +44,10 @@ internal abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISetting private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); - private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, - Context.CurrentPluginMetadata.Name, "Settings.json"); + private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory, + Context.CurrentPluginMetadata.Name); + + private string SettingPath => Path.Combine(SettingDirectory, "Settings.json"); public abstract List LoadContextMenus(Result selectedResult); @@ -159,5 +161,13 @@ public Control CreateSettingPanel() { return Settings.CreateSettingPanel(); } + + public void DeletePluginSettingsDirectory() + { + if (Directory.Exists(SettingDirectory)) + { + Directory.Delete(SettingDirectory, true); + } + } } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 8f2d78d760b..29f1604c4b1 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -210,9 +210,9 @@ public static async Task InitializePluginsAsync(IPublicAPI api) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); API.ShowMsg( - InternationalizationManager.Instance.GetTranslation("failedToInitializePluginsTitle"), + API.GetTranslation("failedToInitializePluginsTitle"), string.Format( - InternationalizationManager.Instance.GetTranslation("failedToInitializePluginsMessage"), + API.GetTranslation("failedToInitializePluginsMessage"), failed ), "", @@ -439,7 +439,7 @@ public static bool PluginModified(string uuid) public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { InstallPlugin(newVersion, zipFilePath, checkModified:false); - UninstallPlugin(existingVersion, removeSettings:false, checkModified:false); + UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); _modifiedPlugins.Add(existingVersion.ID); } @@ -454,9 +454,9 @@ public static void InstallPlugin(UserPlugin plugin, string zipFilePath) /// /// Uninstall a plugin. /// - public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true) + public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) { - UninstallPlugin(plugin, removeSettings, true); + UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true); } #endregion @@ -521,7 +521,15 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c FilesFolders.CopyAll(pluginFolderPath, newPluginPath, MessageBoxEx.Show); - Directory.Delete(tempFolderPluginPath, true); + try + { + if (Directory.Exists(tempFolderPluginPath)) + Directory.Delete(tempFolderPluginPath, true); + } + catch (Exception e) + { + Log.Exception($"|PluginManager.InstallPlugin|Failed to delete temp folder {tempFolderPluginPath}", e); + } if (checkModified) { @@ -529,14 +537,62 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c } } - internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified) + internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) { if (checkModified && PluginModified(plugin.ID)) { throw new ArgumentException($"Plugin {plugin.Name} has been modified"); } - if (removeSettings) + if (removePluginSettings) + { + if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader + { + var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath); + var assembly = assemblyLoader.LoadAssemblyAndDependencies(); + var assemblyName = assembly.GetName().Name; + + // if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin + // so we need to remove it from the api instance + var method = API.GetType().GetMethod("RemovePluginSettings"); + var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName }); + + // if there exists a json storage for current plugin, we need to delete the directory path + if (pluginJsonStorage != null) + { + var deleteMethod = pluginJsonStorage.GetType().GetMethod("DeleteDirectory"); + try + { + deleteMethod?.Invoke(pluginJsonStorage, null); + } + catch (Exception e) + { + Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e); + API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"), + string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); + } + } + } + else // the plugin with json prc interface + { + var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID); + if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin) + { + try + { + jsonRpcPlugin.DeletePluginSettingsDirectory(); + } + catch (Exception e) + { + Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e); + API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"), + string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); + } + } + } + } + + if (removePluginFromSettings) { Settings.Plugins.Remove(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index abe3f55b5ad..bc3900da802 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -3,14 +3,17 @@ namespace Flow.Launcher.Infrastructure.Storage { - public class PluginJsonStorage :JsonStorage where T : new() + public class PluginJsonStorage : JsonStorage where T : new() { + // Use assembly name to check which plugin is using this storage + public readonly string AssemblyName; + public PluginJsonStorage() { // C# related, add python related below var dataType = typeof(T); - var assemblyName = dataType.Assembly.GetName().Name; - DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, assemblyName); + AssemblyName = dataType.Assembly.GetName().Name; + DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName); Helper.ValidateDirectory(DirectoryPath); FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); @@ -20,6 +23,13 @@ public PluginJsonStorage(T data) : this() { Data = data; } + + public void DeleteDirectory() + { + if (Directory.Exists(DirectoryPath)) + { + Directory.Delete(DirectoryPath, true); + } + } } } - diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 00523d03de3..c66772c83c5 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -129,7 +129,8 @@ Version Website Uninstall - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Plugin Store @@ -146,8 +147,6 @@ This plugin has been updated within the last 7 days New Update is Available - - Theme Appearance @@ -197,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Hotkey Hotkeys diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 7706a64ba9d..e5bc74958d7 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -189,6 +189,23 @@ public void LogException(string className, string message, Exception e, private readonly ConcurrentDictionary _pluginJsonStorages = new(); + public object RemovePluginSettings(string assemblyName) + { + foreach (var keyValuePair in _pluginJsonStorages) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString(); + if (name == assemblyName) + { + _pluginJsonStorages.Remove(key, out var pluginJsonStorage); + return pluginJsonStorage; + } + } + + return null; + } + /// /// Save plugin settings. /// diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index de6a3a2fb4c..573ca90519e 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -15,6 +15,8 @@ Installing Plugin Download and install {0} Plugin Uninstall + Keep plugin settings + Do you want to keep the settings of the plugin for the next usage? Plugin {0} successfully installed. Restarting Flow, please wait... Unable to find the plugin.json metadata file from the extracted zip file. Error: A plugin which has the same or greater version with {0} already exists. @@ -37,13 +39,13 @@ 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. {0} plugins 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 diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index c1ed904b337..f4c8a66da31 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -733,7 +733,11 @@ private void Uninstall(PluginMetadata plugin) { try { - PluginManager.UninstallPlugin(plugin, removeSettings: true); + var removePluginSettings = Context.API.ShowMsgBox( + Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"), + Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"), + button: MessageBoxButton.YesNo) == MessageBoxResult.No; + PluginManager.UninstallPlugin(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings); } catch (ArgumentException e) {