diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 451df6147fa..7e9cc9a484a 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -4,6 +4,7 @@ using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Forms; @@ -116,7 +117,10 @@ private IEnumerable SetPathForPluginPairs(string filePath, string la foreach (var metadata in PluginMetadataList) { if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) + { + metadata.AssemblyName = string.Empty; pluginPairs.Add(CreatePluginPair(filePath, metadata)); + } } return pluginPairs; diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 97c3c898121..88d595301be 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -1,28 +1,16 @@ using Flow.Launcher.Core.Resource; -using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Microsoft.IO; using System.Windows; -using System.Windows.Controls; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; -using CheckBox = System.Windows.Controls.CheckBox; -using Control = System.Windows.Controls.Control; -using Orientation = System.Windows.Controls.Orientation; -using TextBox = System.Windows.Controls.TextBox; -using UserControl = System.Windows.Controls.UserControl; -using System.Windows.Documents; namespace Flow.Launcher.Core.Plugin { @@ -42,7 +30,7 @@ internal abstract class JsonRPCPlugin : JsonRPCPluginBase private int RequestId { get; set; } private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); - private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json"); + private string SettingPath => Path.Combine(Context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "Settings.json"); public override List LoadContextMenus(Result selectedResult) { diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs index 7248c625947..779dcf887d8 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs @@ -1,32 +1,15 @@ using Flow.Launcher.Core.Resource; -using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Microsoft.IO; -using System.Windows; -using System.Windows.Controls; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; -using CheckBox = System.Windows.Controls.CheckBox; using Control = System.Windows.Controls.Control; -using Orientation = System.Windows.Controls.Orientation; -using TextBox = System.Windows.Controls.TextBox; -using UserControl = System.Windows.Controls.UserControl; -using System.Windows.Documents; -using static System.Windows.Forms.LinkLabel; -using Droplex; -using System.Windows.Forms; -using Microsoft.VisualStudio.Threading; namespace Flow.Launcher.Core.Plugin { @@ -44,8 +27,7 @@ public abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISettingPr private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); - private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory, - Context.CurrentPluginMetadata.Name); + private string SettingDirectory => Context.CurrentPluginMetadata.PluginSettingsDirectoryPath; private string SettingPath => Path.Combine(SettingDirectory, "Settings.json"); @@ -166,13 +148,5 @@ public Control CreateSettingPanel() { return Settings.CreateSettingPanel(); } - - public void DeletePluginSettingsDirectory() - { - if (Directory.Exists(SettingDirectory)) - { - Directory.Delete(SettingDirectory, true); - } - } } } diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs index dd6517a7fd1..163f970469d 100644 --- a/Flow.Launcher.Core/Plugin/PluginConfig.cs +++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.IO; @@ -9,7 +9,6 @@ namespace Flow.Launcher.Core.Plugin { - internal abstract class PluginConfig { /// @@ -112,7 +111,7 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory) metadata = JsonSerializer.Deserialize(File.ReadAllText(configPath)); metadata.PluginDirectory = pluginDirectory; // for plugins which doesn't has ActionKeywords key - metadata.ActionKeywords = metadata.ActionKeywords ?? new List { metadata.ActionKeyword }; + metadata.ActionKeywords ??= new List { metadata.ActionKeyword }; // for plugin still use old ActionKeyword metadata.ActionKeyword = metadata.ActionKeywords?[0]; } @@ -137,4 +136,4 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory) return metadata; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index b1396e20712..578243139c6 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -35,7 +35,7 @@ public static class PluginManager private static PluginsSettings Settings; private static List _metadatas; - private static List _modifiedPlugins = new List(); + private static List _modifiedPlugins = new(); /// /// Directories that will hold Flow Launcher plugin directory @@ -72,15 +72,20 @@ public static async ValueTask DisposePluginsAsync() { foreach (var pluginPair in AllPlugins) { - switch (pluginPair.Plugin) - { - case IDisposable disposable: - disposable.Dispose(); - break; - case IAsyncDisposable asyncDisposable: - await asyncDisposable.DisposeAsync(); - break; - } + await DisposePluginAsync(pluginPair); + } + } + + private static async Task DisposePluginAsync(PluginPair pluginPair) + { + switch (pluginPair.Plugin) + { + case IDisposable disposable: + disposable.Dispose(); + break; + case IAsyncDisposable asyncDisposable: + await asyncDisposable.DisposeAsync(); + break; } } @@ -155,6 +160,25 @@ public static void LoadPlugins(PluginsSettings settings) Settings = settings; Settings.UpdatePluginSettings(_metadatas); AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); + // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins + UpdatePluginDirectory(_metadatas); + } + + private static void UpdatePluginDirectory(List metadatas) + { + foreach (var metadata in metadatas) + { + if (AllowedLanguage.IsDotNet(metadata.Language)) + { + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName); + } + else + { + metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); + metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); + } + } } /// @@ -228,10 +252,9 @@ public static ICollection ValidPluginsForQuery(Query query) if (query is null) return Array.Empty(); - if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword)) + if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin)) return GlobalPlugins; - var plugin = NonGlobalPlugins[query.ActionKeyword]; return new List { plugin @@ -445,10 +468,10 @@ public static bool PluginModified(string uuid) /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, /// unless it's a local path installation /// - public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) + public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { InstallPlugin(newVersion, zipFilePath, checkModified:false); - UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); + await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false); _modifiedPlugins.Add(existingVersion.ID); } @@ -463,9 +486,9 @@ public static void InstallPlugin(UserPlugin plugin, string zipFilePath) /// /// Uninstall a plugin. /// - public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) + public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) { - UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true); + await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true); } #endregion @@ -546,63 +569,62 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c } } - internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) + internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified) { if (checkModified && PluginModified(plugin.ID)) { throw new ArgumentException($"Plugin {plugin.Name} has been modified"); } - if (removePluginSettings) + if (removePluginSettings || removePluginFromSettings) { - if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader + // If we want to remove plugin from AllPlugins, + // we need to dispose them so that they can release file handles + // which can help FL to delete the plugin settings & cache folders successfully + var pluginPairs = AllPlugins.FindAll(p => p.Metadata.ID == plugin.ID); + foreach (var pluginPair in pluginPairs) { - var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath); - var assembly = assemblyLoader.LoadAssemblyAndDependencies(); - var assemblyName = assembly.GetName().Name; + await DisposePluginAsync(pluginPair); + } + } - // 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 + if (removePluginSettings) + { + // For dotnet plugins, we need to remove their PluginJsonStorage instance + if (AllowedLanguage.IsDotNet(plugin.Language)) + { var method = API.GetType().GetMethod("RemovePluginSettings"); - var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName }); + method?.Invoke(API, new object[] { plugin.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)); - } - } + try + { + var pluginSettingsDirectory = plugin.PluginSettingsDirectoryPath; + if (Directory.Exists(pluginSettingsDirectory)) + Directory.Delete(pluginSettingsDirectory, true); } - else // the plugin with json prc interface + catch (Exception e) { - 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)); - } - } + Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e); + API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"), + string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name)); } } if (removePluginFromSettings) { + try + { + var pluginCacheDirectory = plugin.PluginCacheDirectoryPath; + if (Directory.Exists(pluginCacheDirectory)) + Directory.Delete(pluginCacheDirectory, true); + } + catch (Exception e) + { + Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin cache folder for {plugin.Name}", e); + API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"), + string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); + } Settings.Plugins.Remove(plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); } diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 4827cf69d41..a64457ffc53 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -74,9 +74,11 @@ public static IEnumerable DotNetPlugins(List source) typeof(IAsyncPlugin)); plugin = Activator.CreateInstance(type) as IAsyncPlugin; + + metadata.AssemblyName = assembly.GetName().Name; } #if DEBUG - catch (Exception e) + catch (Exception) { throw; } @@ -112,7 +114,7 @@ public static IEnumerable DotNetPlugins(List source) if (erroredPlugins.Count > 0) { - var errorPluginString = String.Join(Environment.NewLine, erroredPlugins); + var errorPluginString = string.Join(Environment.NewLine, erroredPlugins); var errorMessage = "The following " + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") @@ -134,9 +136,13 @@ public static IEnumerable ExecutablePlugins(IEnumerable o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase)) - .Select(metadata => new PluginPair + .Select(metadata => { - Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata + return new PluginPair + { + Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), + Metadata = metadata + }; }); } @@ -144,9 +150,13 @@ public static IEnumerable ExecutableV2Plugins(IEnumerable o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase)) - .Select(metadata => new PluginPair + .Select(metadata => { - Plugin = new ExecutablePluginV2(metadata.ExecuteFilePath), Metadata = metadata + return new PluginPair + { + Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), + Metadata = metadata + }; }); } } diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index c86ed432467..d694e0d3595 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -47,6 +47,7 @@ public static class Constant public const string Themes = "Themes"; public const string Settings = "Settings"; public const string Logs = "Logs"; + public const string Cache = "Cache"; public const string Website = "https://flowlauncher.com"; public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher"; diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 7f847e287d2..9f5d6725e2b 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -12,13 +12,13 @@ namespace Flow.Launcher.Infrastructure.Logger { public static class Log { - public const string DirectoryName = "Logs"; + public const string DirectoryName = Constant.Logs; public static string CurrentLogDirectory { get; } static Log() { - CurrentLogDirectory = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Version); + CurrentLogDirectory = DataLocation.VersionLogDirectory; if (!Directory.Exists(CurrentLogDirectory)) { Directory.CreateDirectory(CurrentLogDirectory); diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 2a439b8cce5..5b73faae6a6 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters; -using System.Runtime.Serialization.Formatters.Binary; +using System.IO; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; @@ -16,18 +11,16 @@ namespace Flow.Launcher.Infrastructure.Storage /// Normally, it has better performance, but not readable /// /// - /// It utilize MemoryPack, which means the object must be MemoryPackSerializable - /// https://github.com/Cysharp/MemoryPack + /// It utilize MemoryPack, which means the object must be MemoryPackSerializable /// public class BinaryStorage { - const string DirectoryName = "Cache"; + public const string FileSuffix = ".cache"; - const string FileSuffix = ".cache"; - - public BinaryStorage(string filename) + // Let the derived class to set the file path + public BinaryStorage(string filename, string directoryPath = null) { - var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName); + directoryPath ??= DataLocation.CacheDirectory; Helper.ValidateDirectory(directoryPath); FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); @@ -58,14 +51,14 @@ public async ValueTask TryLoadAsync(T defaultData) } } - private async ValueTask DeserializeAsync(Stream stream, T defaultData) + private static async ValueTask DeserializeAsync(Stream stream, T defaultData) { try { var t = await MemoryPackSerializer.DeserializeAsync(stream); return t; } - catch (System.Exception e) + catch (System.Exception) { // Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); return defaultData; diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 507838d9477..40106acd829 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Infrastructure.Storage protected T? Data; // need a new directory name - public const string DirectoryName = "Settings"; + public const string DirectoryName = Constant.Settings; public const string FileSuffix = ".json"; protected string FilePath { get; init; } = null!; diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index bc3900da802..b377c81aa14 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -13,7 +13,7 @@ public PluginJsonStorage() // C# related, add python related below var dataType = typeof(T); AssemblyName = dataType.Assembly.GetName().Name; - DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName); + DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName); Helper.ValidateDirectory(DirectoryPath); FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); @@ -23,13 +23,5 @@ public PluginJsonStorage(T data) : this() { Data = data; } - - public void DeleteDirectory() - { - if (Directory.Exists(DirectoryPath)) - { - Directory.Delete(DirectoryPath, true); - } - } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index e294f52b8c5..5b948e4508f 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -25,8 +25,16 @@ public static bool PortableDataLocationInUse() return false; } + public static string VersionLogDirectory => Path.Combine(LogDirectory, Constant.Version); + public static string LogDirectory => Path.Combine(DataDirectory(), Constant.Logs); + + public static readonly string CacheDirectory = Path.Combine(DataDirectory(), Constant.Cache); + public static readonly string SettingsDirectory = Path.Combine(DataDirectory(), Constant.Settings); public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); - public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), "Settings", Constant.Plugins); + public static readonly string ThemesDirectory = Path.Combine(DataDirectory(), Constant.Themes); + + public static readonly string PluginSettingsDirectory = Path.Combine(SettingsDirectory, Constant.Plugins); + public static readonly string PluginCacheDirectory = Path.Combine(DataDirectory(), Constant.Cache, Constant.Plugins); public const string PythonEnvironmentName = "Python"; public const string NodeEnvironmentName = "Node.js"; diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index 98f4dccda18..1d06e18f738 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -32,10 +32,8 @@ public void UpdatePluginSettings(List metadatas) { foreach (var metadata in metadatas) { - if (Plugins.ContainsKey(metadata.ID)) + if (Plugins.TryGetValue(metadata.ID, out var settings)) { - var settings = Plugins[metadata.ID]; - if (string.IsNullOrEmpty(settings.Version)) settings.Version = metadata.Version; diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs index e31c8e31d8b..9e05bbd0617 100644 --- a/Flow.Launcher.Plugin/ActionContext.cs +++ b/Flow.Launcher.Plugin/ActionContext.cs @@ -51,6 +51,9 @@ public ModifierKeys ToModifierKeys() (WinPressed ? ModifierKeys.Windows : ModifierKeys.None); } + /// + /// Default object with all keys not pressed. + /// public static readonly SpecialKeyState Default = new () { CtrlPressed = false, ShiftPressed = false, diff --git a/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs b/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs index fd21460ac55..aa4e4a56db5 100644 --- a/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs +++ b/Flow.Launcher.Plugin/Interfaces/IResultUpdated.cs @@ -4,17 +4,42 @@ namespace Flow.Launcher.Plugin { + /// + /// Interface for plugins that want to manually update their results + /// public interface IResultUpdated : IFeatures { + /// + /// Event that is triggered when the results are updated + /// event ResultUpdatedEventHandler ResultsUpdated; } + /// + /// Delegate for the ResultsUpdated event + /// + /// + /// public delegate void ResultUpdatedEventHandler(IResultUpdated sender, ResultUpdatedEventArgs e); + /// + /// Event arguments for the ResultsUpdated event + /// public class ResultUpdatedEventArgs : EventArgs { + /// + /// List of results that should be displayed + /// public List Results; + + /// + /// Query that triggered the update + /// public Query Query; + + /// + /// Token that can be used to cancel the update + /// public CancellationToken Token { get; init; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs b/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs index d5ffba20b9b..f034243c3b4 100644 --- a/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs @@ -2,8 +2,15 @@ namespace Flow.Launcher.Plugin { + /// + /// This interface is used to create settings panel for .Net plugins + /// public interface ISettingProvider { + /// + /// Create settings panel control for .Net plugins + /// + /// Control CreateSettingPanel(); } } diff --git a/Flow.Launcher.Plugin/PluginInitContext.cs b/Flow.Launcher.Plugin/PluginInitContext.cs index f040752bd60..a42e3930cb1 100644 --- a/Flow.Launcher.Plugin/PluginInitContext.cs +++ b/Flow.Launcher.Plugin/PluginInitContext.cs @@ -5,10 +5,18 @@ /// public class PluginInitContext { + /// + /// Default constructor. + /// public PluginInitContext() { } + /// + /// Constructor. + /// + /// + /// public PluginInitContext(PluginMetadata currentPluginMetadata, IPublicAPI api) { CurrentPluginMetadata = currentPluginMetadata; diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index 91256298bc6..42c90bda4a2 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -4,24 +4,77 @@ namespace Flow.Launcher.Plugin { + /// + /// Plugin metadata + /// public class PluginMetadata : BaseModel { - private string _pluginDirectory; + /// + /// Plugin ID. + /// public string ID { get; set; } + + /// + /// Plugin name. + /// public string Name { get; set; } + + /// + /// Plugin author. + /// public string Author { get; set; } + + /// + /// Plugin version. + /// public string Version { get; set; } + + /// + /// Plugin language. + /// See + /// public string Language { get; set; } + + /// + /// Plugin description. + /// public string Description { get; set; } + + /// + /// Plugin website. + /// public string Website { get; set; } + + /// + /// Whether plugin is disabled. + /// public bool Disabled { get; set; } - public string ExecuteFilePath { get; private set;} + /// + /// Plugin execute file path. + /// + public string ExecuteFilePath { get; private set; } + + /// + /// Plugin execute file name. + /// public string ExecuteFileName { get; set; } + /// + /// Plugin assembly name. + /// Only available for .Net plugins. + /// + [JsonIgnore] + public string AssemblyName { get; internal set; } + + private string _pluginDirectory; + + /// + /// Plugin source directory. + /// public string PluginDirectory { - get { return _pluginDirectory; } + get => _pluginDirectory; internal set { _pluginDirectory = value; @@ -30,30 +83,74 @@ internal set } } + /// + /// The first action keyword of plugin. + /// public string ActionKeyword { get; set; } + /// + /// All action keywords of plugin. + /// public List ActionKeywords { get; set; } - + + /// + /// Hide plugin keyword setting panel. + /// public bool HideActionKeywordPanel { get; set; } + /// + /// Plugin icon path. + /// public string IcoPath { get; set;} - - public override string ToString() - { - return Name; - } + /// + /// Plugin priority. + /// [JsonIgnore] public int Priority { get; set; } /// - /// Init time include both plugin load time and init time + /// Init time include both plugin load time and init time. /// [JsonIgnore] public long InitTime { get; set; } + + /// + /// Average query time. + /// [JsonIgnore] public long AvgQueryTime { get; set; } + + /// + /// Query count. + /// [JsonIgnore] public int QueryCount { get; set; } + + /// + /// The path to the plugin settings directory which is not validated. + /// It is used to store plugin settings files and data files. + /// When plugin is deleted, FL will ask users whether to keep its settings. + /// If users do not want to keep, this directory will be deleted. + /// + [JsonIgnore] + public string PluginSettingsDirectoryPath { get; internal set; } + + /// + /// The path to the plugin cache directory which is not validated. + /// It is used to store cache files. + /// When plugin is deleted, this directory will be deleted as well. + /// + [JsonIgnore] + public string PluginCacheDirectoryPath { get; internal set; } + + /// + /// Convert to string. + /// + /// + public override string ToString() + { + return Name; + } } } diff --git a/Flow.Launcher.Plugin/PluginPair.cs b/Flow.Launcher.Plugin/PluginPair.cs index 7bf6346910c..037af7427a6 100644 --- a/Flow.Launcher.Plugin/PluginPair.cs +++ b/Flow.Launcher.Plugin/PluginPair.cs @@ -1,21 +1,37 @@ namespace Flow.Launcher.Plugin { + /// + /// Plugin instance and plugin metadata + /// public class PluginPair { + /// + /// Plugin instance + /// public IAsyncPlugin Plugin { get; internal set; } - public PluginMetadata Metadata { get; internal set; } - + /// + /// Plugin metadata + /// + public PluginMetadata Metadata { get; internal set; } + /// + /// Convert to string + /// + /// public override string ToString() { return Metadata.Name; } + /// + /// Compare by plugin metadata ID + /// + /// + /// public override bool Equals(object obj) { - PluginPair r = obj as PluginPair; - if (r != null) + if (obj is PluginPair r) { return string.Equals(r.Metadata.ID, Metadata.ID); } @@ -25,6 +41,10 @@ public override bool Equals(object obj) } } + /// + /// Get hash coode + /// + /// public override int GetHashCode() { var hashcode = Metadata.ID?.GetHashCode() ?? 0; diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index 15b2dd171f5..913dc31ae65 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -2,10 +2,11 @@ namespace Flow.Launcher.Plugin { + /// + /// Represents a query that is sent to a plugin. + /// public class Query { - public Query() { } - /// /// Raw query, this includes action keyword if it has /// We didn't recommend use this property directly. You should always use Search property. @@ -54,13 +55,13 @@ public Query() { } /// public string ActionKeyword { get; init; } - [JsonIgnore] /// /// Splits by spaces and returns the first item. /// /// /// returns an empty string when does not have enough items. /// + [JsonIgnore] public string FirstSearch => SplitSearch(0); [JsonIgnore] diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 9b16cc1cbf0..9104854389d 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -13,7 +12,6 @@ namespace Flow.Launcher.Plugin /// public class Result { - private string _pluginDirectory; private string _icoPath; diff --git a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs index a7744ffaca6..752c85933d6 100644 --- a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs +++ b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs @@ -6,6 +6,9 @@ namespace Flow.Launcher.Plugin.SharedCommands { + /// + /// Contains methods to open a search in a new browser window or tab. + /// public static class SearchWeb { private static string GetDefaultBrowserPath() @@ -106,4 +109,4 @@ public static void OpenInBrowserTab(this string url, string browserPath = "", bo } } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index a0440e30de9..288222d4ff1 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -8,12 +8,26 @@ namespace Flow.Launcher.Plugin.SharedCommands { + /// + /// Contains methods for running shell commands + /// public static class ShellCommand { + /// + /// Delegate for EnumThreadWindows + /// + /// + /// + /// public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); private static bool containsSecurityWindow; + /// + /// Runs a windows command using the provided ProcessStartInfo + /// + /// + /// public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo) { processStartInfo.Verb = "RunAsUser"; @@ -65,6 +79,15 @@ private static unsafe string GetWindowTitle(HWND hwnd) return buffer[..length].ToString(); } + /// + /// Runs a windows command using the provided ProcessStartInfo + /// + /// + /// + /// + /// + /// + /// public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false) { diff --git a/Flow.Launcher.Plugin/SharedModels/MatchResult.cs b/Flow.Launcher.Plugin/SharedModels/MatchResult.cs index 5144eb61d66..36677d4bb29 100644 --- a/Flow.Launcher.Plugin/SharedModels/MatchResult.cs +++ b/Flow.Launcher.Plugin/SharedModels/MatchResult.cs @@ -2,14 +2,29 @@ namespace Flow.Launcher.Plugin.SharedModels { + /// + /// Represents the result of a match operation. + /// public class MatchResult { + /// + /// Initializes a new instance of the class. + /// + /// + /// public MatchResult(bool success, SearchPrecisionScore searchPrecision) { Success = success; SearchPrecision = searchPrecision; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// public MatchResult(bool success, SearchPrecisionScore searchPrecision, List matchData, int rawScore) { Success = success; @@ -18,6 +33,9 @@ public MatchResult(bool success, SearchPrecisionScore searchPrecision, List RawScore = rawScore; } + /// + /// Whether the match operation was successful. + /// public bool Success { get; set; } /// @@ -30,6 +48,9 @@ public MatchResult(bool success, SearchPrecisionScore searchPrecision, List /// private int _rawScore; + /// + /// The raw calculated search score without any search precision filtering applied. + /// public int RawScore { get { return _rawScore; } @@ -45,8 +66,15 @@ public int RawScore /// public List MatchData { get; set; } + /// + /// The search precision score used to filter the search results. + /// public SearchPrecisionScore SearchPrecision { get; set; } + /// + /// Determines if the search precision score is met. + /// + /// public bool IsSearchPrecisionScoreMet() { return IsSearchPrecisionScoreMet(_rawScore); @@ -63,10 +91,24 @@ private int ScoreAfterSearchPrecisionFilter(int rawScore) } } + /// + /// Represents the search precision score used to filter search results. + /// public enum SearchPrecisionScore { + /// + /// The highest search precision score. + /// Regular = 50, + + /// + /// The medium search precision score. + /// Low = 20, + + /// + /// The lowest search precision score. + /// None = 0 } } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index f0454b49633..e5a73f3402b 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -138,6 +138,8 @@ Uninstall Fail to remove plugin settings Plugins: {0} - Fail to remove plugin settings files, please remove them manually + Fail to remove plugin cache + Plugins: {0} - Fail to remove plugin cache files, please remove them manually Plugin Store diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 456f1ad4760..e19ad2fdcd4 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -192,7 +192,7 @@ public void LogException(string className, string message, Exception e, private readonly ConcurrentDictionary _pluginJsonStorages = new(); - public object RemovePluginSettings(string assemblyName) + public void RemovePluginSettings(string assemblyName) { foreach (var keyValuePair in _pluginJsonStorages) { @@ -202,11 +202,8 @@ public object RemovePluginSettings(string assemblyName) if (name == assemblyName) { _pluginJsonStorages.Remove(key, out var pluginJsonStorage); - return pluginJsonStorage; } } - - return null; } /// diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index f82f8e34d48..20f905411d2 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -102,13 +102,13 @@ private void AskClearLogFolderConfirmation() [RelayCommand] private void OpenSettingsFolder() { - App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings)); + App.API.OpenDirectory(DataLocation.SettingsDirectory); } [RelayCommand] private void OpenParentOfSettingsFolder(object parameter) { - string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings); + string settingsFolderPath = Path.Combine(DataLocation.SettingsDirectory); string parentFolderPath = Path.GetDirectoryName(settingsFolderPath); App.API.OpenDirectory(parentFolderPath); } @@ -140,7 +140,7 @@ private void ClearLogFolder() private static DirectoryInfo GetLogDir(string version = "") { - return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version)); + return new DirectoryInfo(Path.Combine(DataLocation.LogDirectory, version)); } private static List GetLogFiles(string version = "") diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 61d365b649c..b8738efe4ad 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -467,7 +467,7 @@ public SettingsPaneThemeViewModel(Settings settings) [RelayCommand] private void OpenThemesFolder() { - App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); + App.API.OpenDirectory(DataLocation.ThemesDirectory); } [RelayCommand] diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 4ceadec56fc..79d6aedd5a9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -341,7 +341,7 @@ await DownloadFileAsync( } else { - PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, + await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); if (Settings.AutoRestartAfterChanging) @@ -433,7 +433,7 @@ await DownloadFileAsync( if (cts.IsCancellationRequested) return; else - PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, + await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath); } catch (Exception ex) @@ -684,7 +684,7 @@ internal List RequestUninstall(string search) Title = $"{x.Metadata.Name} by {x.Metadata.Author}", SubTitle = x.Metadata.Description, IcoPath = x.Metadata.IcoPath, - Action = e => + AsyncAction = async e => { string message; if (Settings.AutoRestartAfterChanging) @@ -707,7 +707,7 @@ internal List RequestUninstall(string search) MessageBoxButton.YesNo) == MessageBoxResult.Yes) { Context.API.HideMainWindow(); - Uninstall(x.Metadata); + await UninstallAsync(x.Metadata); if (Settings.AutoRestartAfterChanging) { Context.API.RestartApp(); @@ -732,7 +732,7 @@ internal List RequestUninstall(string search) return Search(results, search); } - private void Uninstall(PluginMetadata plugin) + private async Task UninstallAsync(PluginMetadata plugin) { try { @@ -740,7 +740,7 @@ private void Uninstall(PluginMetadata plugin) 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); + await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings); } catch (ArgumentException e) { diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index b3763aaa69c..3be23214c86 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.Program.Programs; using Flow.Launcher.Plugin.Program.Views; using Flow.Launcher.Plugin.Program.Views.Models; @@ -188,9 +191,61 @@ public async Task InitAsync(PluginInitContext context) await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { - _win32Storage = new BinaryStorage("Win32"); + Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath); + + static void MoveFile(string sourcePath, string destinationPath) + { + if (!File.Exists(sourcePath)) + { + return; + } + + if (File.Exists(destinationPath)) + { + try + { + File.Delete(sourcePath); + } + catch (Exception) + { + // Ignore, we will handle next time we start the plugin + } + return; + } + + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (!Directory.Exists(destinationDirectory) && (!string.IsNullOrEmpty(destinationDirectory))) + { + try + { + Directory.CreateDirectory(destinationDirectory); + } + catch (Exception) + { + // Ignore, we will handle next time we start the plugin + } + } + try + { + File.Move(sourcePath, destinationPath); + } + catch (Exception) + { + // Ignore, we will handle next time we start the plugin + } + } + + // Move old cache files to the new cache directory + var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"Win32.cache"); + var newWin32CacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"Win32.cache"); + MoveFile(oldWin32CacheFile, newWin32CacheFile); + var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"UWP.cache"); + var newUWPCacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"UWP.cache"); + MoveFile(oldUWPCacheFile, newUWPCacheFile); + + _win32Storage = new BinaryStorage("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _win32s = await _win32Storage.TryLoadAsync(Array.Empty()); - _uwpStorage = new BinaryStorage("UWP"); + _uwpStorage = new BinaryStorage("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _uwps = await _uwpStorage.TryLoadAsync(Array.Empty()); }); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 77f3b56a6e2..e629f887ee1 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Windows; @@ -191,8 +190,6 @@ private static unsafe bool EnableShutdownPrivilege() private List Commands() { var results = new List(); - var logPath = Path.Combine(DataLocation.DataDirectory(), "Logs", Constant.Version); - var userDataPath = DataLocation.DataDirectory(); var recycleBinFolder = "shell:RecycleBinFolder"; results.AddRange(new[] { @@ -433,11 +430,11 @@ private List Commands() { Title = "Open Log Location", IcoPath = "Images\\app.png", - CopyText = logPath, - AutoCompleteText = logPath, + CopyText = DataLocation.VersionLogDirectory, + AutoCompleteText = DataLocation.VersionLogDirectory, Action = c => { - _context.API.OpenDirectory(logPath); + _context.API.OpenDirectory(DataLocation.VersionLogDirectory); return true; } }, @@ -457,11 +454,11 @@ private List Commands() { Title = "Flow Launcher UserData Folder", IcoPath = "Images\\app.png", - CopyText = userDataPath, - AutoCompleteText = userDataPath, + CopyText = DataLocation.DataDirectory(), + AutoCompleteText = DataLocation.DataDirectory(), Action = c => { - _context.API.OpenDirectory(userDataPath); + _context.API.OpenDirectory(DataLocation.DataDirectory()); return true; } }, diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index ce53c7da5c4..7ad9715bbd8 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.WebSearch @@ -183,9 +182,8 @@ void Init() DefaultImagesDirectory = Path.Combine(pluginDirectory, Images); Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory); - // Custom images directory is in the WebSearch's data location folder - var name = Path.GetFileNameWithoutExtension(_context.CurrentPluginMetadata.ExecuteFileName); - CustomImagesDirectory = Path.Combine(DataLocation.PluginSettingsDirectory, name, "CustomIcons"); + // Custom images directory is in the WebSearch's data location folder + CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons"); }; }