From 6f0126d3ba3f4ab8c6928141287391602d77cf1f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 11:47:18 +0800 Subject: [PATCH 01/46] Add log error api function for csharp and jsonrpc --- .../Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs | 8 ++++++-- Flow.Launcher.Infrastructure/Logger/Log.cs | 11 ++--------- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 5 +++++ Flow.Launcher/PublicAPIInstance.cs | 3 +++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs index 8df2ce9ed..102d0089f 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs @@ -12,7 +12,7 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models { public class JsonRPCPublicAPI { - private IPublicAPI _api; + private readonly IPublicAPI _api; public JsonRPCPublicAPI(IPublicAPI api) { @@ -104,7 +104,6 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models return _api.GetAllPlugins(); } - public MatchResult FuzzySearch(string query, string stringToCompare) { return _api.FuzzySearch(query, stringToCompare); @@ -156,6 +155,11 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models _api.LogWarn(className, message, methodName); } + public void LogError(string className, string message, [CallerMemberName] string methodName = "") + { + _api.LogError(className, message, methodName); + } + public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null) { _api.OpenDirectory(DirectoryPath, FileNameOrFilePath); diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 9f5d6725e..9e1173f34 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -1,12 +1,12 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using Flow.Launcher.Infrastructure.UserSettings; using NLog; using NLog.Config; using NLog.Targets; -using Flow.Launcher.Infrastructure.UserSettings; using NLog.Targets.Wrappers; -using System.Runtime.ExceptionServices; namespace Flow.Launcher.Infrastructure.Logger { @@ -135,13 +135,6 @@ namespace Flow.Launcher.Infrastructure.Logger return className; } - private static void ExceptionInternal(string classAndMethod, string message, System.Exception e) - { - var logger = LogManager.GetLogger(classAndMethod); - - logger.Error(e, message); - } - private static void LogInternal(string message, LogLevel level) { if (FormatValid(message)) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f178ebb90..9258e5147 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -227,6 +227,11 @@ namespace Flow.Launcher.Plugin /// void LogWarn(string className, string message, [CallerMemberName] string methodName = ""); + /// + /// Log error message + /// + void LogError(string className, string message, [CallerMemberName] string methodName = ""); + /// /// Log an Exception. Will throw if in debug mode so developer will be aware, /// otherwise logs the eror message. This is the primary logging method used for Flow diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e19ad2fdc..31307b668 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -187,6 +187,9 @@ namespace Flow.Launcher public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") => Log.Warn(className, message, methodName); + public void LogError(string className, string message, [CallerMemberName] string methodName = "") => + Log.Error(className, message, methodName); + public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName); From 1381248e9e71444b56704548167830010f97ac68 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 11:54:18 +0800 Subject: [PATCH 02/46] Adjust code formats --- Flow.Launcher/PublicAPIInstance.cs | 35 ++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 31307b668..2d2713e79 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -11,9 +11,9 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; using CommunityToolkit.Mvvm.DependencyInjection; -using Squirrel; using Flow.Launcher.Core; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; @@ -27,7 +27,7 @@ using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.ViewModel; using JetBrains.Annotations; -using Flow.Launcher.Core.Resource; +using Squirrel; namespace Flow.Launcher { @@ -78,7 +78,11 @@ namespace Flow.Launcher public bool IsMainWindowVisible() => _mainVM.MainWindowVisibilityStatus; - public event VisibilityChangedEventHandler VisibilityChanged { add => _mainVM.VisibilityChanged += value; remove => _mainVM.VisibilityChanged -= value; } + public event VisibilityChangedEventHandler VisibilityChanged + { + add => _mainVM.VisibilityChanged += value; + remove => _mainVM.VisibilityChanged -= value; + } // Must use Ioc.Default.GetRequiredService() to avoid circular dependency public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService().UpdateAppAsync(false); @@ -162,13 +166,14 @@ namespace Flow.Launcher public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); - public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url, token); + public Task HttpGetStringAsync(string url, CancellationToken token = default) => + Http.GetAsync(url, token); public Task HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url, token); public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, - CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token); + CancellationToken token = default) =>Http.DownloadAsync(url, filePath, reportProgress, token); public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); @@ -190,8 +195,8 @@ namespace Flow.Launcher public void LogError(string className, string message, [CallerMemberName] string methodName = "") => Log.Error(className, message, methodName); - public void LogException(string className, string message, Exception e, - [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName); + public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => + Log.Exception(className, message, e, methodName); private readonly ConcurrentDictionary _pluginJsonStorages = new(); @@ -204,7 +209,7 @@ namespace Flow.Launcher var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString(); if (name == assemblyName) { - _pluginJsonStorages.Remove(key, out var pluginJsonStorage); + _pluginJsonStorages.Remove(key, out var _); } } } @@ -333,17 +338,23 @@ namespace Flow.Launcher private readonly List> _globalKeyboardHandlers = new(); - public void RegisterGlobalKeyboardCallback(Func callback) => _globalKeyboardHandlers.Add(callback); - public void RemoveGlobalKeyboardCallback(Func callback) => _globalKeyboardHandlers.Remove(callback); + public void RegisterGlobalKeyboardCallback(Func callback) => + _globalKeyboardHandlers.Add(callback); + + public void RemoveGlobalKeyboardCallback(Func callback) => + _globalKeyboardHandlers.Remove(callback); public void ReQuery(bool reselect = true) => _mainVM.ReQuery(reselect); public void BackToQueryResults() => _mainVM.BackToQueryResults(); - public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) => + public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", + MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, + MessageBoxResult defaultResult = MessageBoxResult.OK) => MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult); - public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); + public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, + Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); #endregion From 835e4096c8ec781e6bd319946853060f9de2b493 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 11:56:51 +0800 Subject: [PATCH 03/46] Fix build issue --- Flow.Launcher.Infrastructure/Logger/Log.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 9e1173f34..84331ef70 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -135,6 +135,15 @@ namespace Flow.Launcher.Infrastructure.Logger return className; } +#if !DEBUG + private static void ExceptionInternal(string classAndMethod, string message, System.Exception e) + { + var logger = LogManager.GetLogger(classAndMethod); + + logger.Error(e, message); + } +#endif + private static void LogInternal(string message, LogLevel level) { if (FormatValid(message)) From 24327533c6e165f0cfa02e9a822fcf973c1880df Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 14:16:32 +0800 Subject: [PATCH 04/46] Add plugin json storage --- .../Image/ImageLoader.cs | 1 + .../Storage/BinaryStorage.cs | 62 +++++++++++++++---- .../Storage/PluginBinaryStorage.cs | 15 +++++ .../Storage/PluginJsonStorage.cs | 6 -- 4 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 6f7b1cd90..cca4bd2a4 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -37,6 +37,7 @@ namespace Flow.Launcher.Infrastructure.Image _hashGenerator = new ImageHashGenerator(); var usage = await LoadStorageToConcurrentDictionaryAsync(); + _storage.ClearData(); ImageCache.Initialize(usage); diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 5b73faae6..69ac6000b 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -4,6 +4,8 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using MemoryPack; +#nullable enable + namespace Flow.Launcher.Infrastructure.Storage { /// @@ -15,40 +17,53 @@ namespace Flow.Launcher.Infrastructure.Storage /// public class BinaryStorage { + protected T? Data; + public const string FileSuffix = ".cache"; - // Let the derived class to set the file path - public BinaryStorage(string filename, string directoryPath = null) - { - directoryPath ??= DataLocation.CacheDirectory; - Helper.ValidateDirectory(directoryPath); + protected string FilePath { get; init; } = null!; - FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); + protected string DirectoryPath { get; init; } = null!; + + // Let the derived class to set the file path + protected BinaryStorage() + { } - public string FilePath { get; } + public BinaryStorage(string filename) + { + DirectoryPath = DataLocation.CacheDirectory; + Helper.ValidateDirectory(DirectoryPath); + + FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}"); + } public async ValueTask TryLoadAsync(T defaultData) { + if (Data != null) + return Data; + if (File.Exists(FilePath)) { if (new FileInfo(FilePath).Length == 0) { Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>"); - await SaveAsync(defaultData); - return defaultData; + Data = defaultData; + await SaveAsync(); } await using var stream = new FileStream(FilePath, FileMode.Open); var d = await DeserializeAsync(stream, defaultData); - return d; + Data = d; } else { Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data"); - await SaveAsync(defaultData); - return defaultData; + Data = defaultData; + await SaveAsync(); } + + return Data; } private static async ValueTask DeserializeAsync(Stream stream, T defaultData) @@ -56,7 +71,7 @@ namespace Flow.Launcher.Infrastructure.Storage try { var t = await MemoryPackSerializer.DeserializeAsync(stream); - return t; + return t ?? defaultData; } catch (System.Exception) { @@ -65,6 +80,27 @@ namespace Flow.Launcher.Infrastructure.Storage } } + public async ValueTask SaveAsync() + { + await using var stream = new FileStream(FilePath, FileMode.Create); + await MemoryPackSerializer.SerializeAsync(stream, Data); + } + + // For SavePluginSettings function + public void Save() + { + var serialized = MemoryPackSerializer.Serialize(Data); + + File.WriteAllBytes(FilePath, serialized); + } + + // ImageCache need to be converted into concurrent dictionary, so it does not need to cache loading results into Data + public void ClearData() + { + Data = default; + } + + // ImageCache storages data in its class, so it needs to pass it to SaveAsync public async ValueTask SaveAsync(T data) { await using var stream = new FileStream(FilePath, FileMode.Create); diff --git a/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs new file mode 100644 index 000000000..87f51d5d7 --- /dev/null +++ b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Flow.Launcher.Infrastructure.Storage +{ + public class PluginBinaryStorage : BinaryStorage where T : new() + { + public PluginBinaryStorage(string cacheName, string cacheDirectory) + { + DirectoryPath = cacheDirectory; + Helper.ValidateDirectory(DirectoryPath); + + FilePath = Path.Combine(DirectoryPath, $"{cacheName}{FileSuffix}"); + } + } +} diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index b377c81aa..9c6547c66 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -10,7 +10,6 @@ namespace Flow.Launcher.Infrastructure.Storage public PluginJsonStorage() { - // C# related, add python related below var dataType = typeof(T); AssemblyName = dataType.Assembly.GetName().Name; DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName); @@ -18,10 +17,5 @@ namespace Flow.Launcher.Infrastructure.Storage FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); } - - public PluginJsonStorage(T data) : this() - { - Data = data; - } } } From ca221d710026b3e402873a174696a48e28e5d958 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 14:19:59 +0800 Subject: [PATCH 05/46] Add binary storage api functions --- .../JsonRPCV2Models/JsonRPCPublicAPI.cs | 5 ++ Flow.Launcher.Core/Plugin/PluginManager.cs | 5 +- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 33 ++++++++++++ Flow.Launcher.Plugin/Interfaces/ISavable.cs | 7 +-- Flow.Launcher/PublicAPIInstance.cs | 53 ++++++++++++++++--- 5 files changed, 91 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs index 8df2ce9ed..cf1c57f3e 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs @@ -185,5 +185,10 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models { _api.StopLoadingBar(); } + + public void SavePluginCaches() + { + _api.SavePluginCaches(); + } } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 4f869901c..fa4e43e07 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -66,6 +66,7 @@ namespace Flow.Launcher.Core.Plugin } API.SavePluginSettings(); + API.SavePluginCaches(); } public static async ValueTask DisposePluginsAsync() @@ -587,11 +588,13 @@ namespace Flow.Launcher.Core.Plugin if (removePluginSettings) { - // For dotnet plugins, we need to remove their PluginJsonStorage instance + // For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances if (AllowedLanguage.IsDotNet(plugin.Language)) { var method = API.GetType().GetMethod("RemovePluginSettings"); method?.Invoke(API, new object[] { plugin.AssemblyName }); + var method1 = API.GetType().GetMethod("RemovePluginCache"); + method1?.Invoke(API, new object[] { plugin.PluginCacheDirectoryPath }); } try diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f178ebb90..be776b068 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -344,5 +344,38 @@ namespace Flow.Launcher.Plugin /// Stop the loading bar in main window /// public void StopLoadingBar(); + + /// + /// Save all Flow's plugins caches + /// + void SavePluginCaches(); + + /// + /// Load BinaryStorage for current plugin's cache. This is the method used to load cache from binary in Flow. + /// When the file is not exist, it will create a new instance for the specific type. + /// + /// Type for deserialization + /// Cache file name + /// Cache directory from plugin metadata + /// Default data to return + /// + /// + /// BinaryStorage utilize MemoryPack, which means the object must be MemoryPackSerializable + /// + Task LoadCacheBinaryStorageAsync(string cacheName, string cacheDirectory, T defaultData) where T : new(); + + /// + /// Save BinaryStorage for current plugin's cache. This is the method used to save cache to binary in Flow.Launcher + /// This method will save the original instance loaded with LoadCacheBinaryStorageAsync. + /// This API call is for manually Save. Flow will automatically save all cache type that has called LoadCacheBinaryStorageAsync or SaveCacheBinaryStorageAsync previously. + /// + /// Type for Serialization + /// Cache file name + /// Cache directory from plugin metadata + /// + /// + /// BinaryStorage utilize MemoryPack, which means the object must be MemoryPackSerializable + /// + Task SaveCacheBinaryStorageAsync(string cacheName, string cacheDirectory) where T : new(); } } diff --git a/Flow.Launcher.Plugin/Interfaces/ISavable.cs b/Flow.Launcher.Plugin/Interfaces/ISavable.cs index 77bd304e4..cabd26962 100644 --- a/Flow.Launcher.Plugin/Interfaces/ISavable.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISavable.cs @@ -1,11 +1,12 @@ -namespace Flow.Launcher.Plugin +namespace Flow.Launcher.Plugin { /// /// Inherit this interface if additional data e.g. cache needs to be saved. /// /// /// For storing plugin settings, prefer - /// or . + /// or . + /// or . /// Once called, your settings will be automatically saved by Flow. /// public interface ISavable : IFeatures @@ -15,4 +16,4 @@ namespace Flow.Launcher.Plugin /// void Save(); } -} \ No newline at end of file +} diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e19ad2fdc..17d7e103e 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -236,14 +236,6 @@ namespace Flow.Launcher ((PluginJsonStorage)_pluginJsonStorages[type]).Save(); } - public void SaveJsonStorage(T settings) where T : new() - { - var type = typeof(T); - _pluginJsonStorages[type] = new PluginJsonStorage(settings); - - ((PluginJsonStorage)_pluginJsonStorages[type]).Save(); - } - public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null) { using var explorer = new Process(); @@ -342,6 +334,51 @@ namespace Flow.Launcher public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); + private readonly ConcurrentDictionary<(string, string, Type), object> _pluginBinaryStorages = new(); + + public void RemovePluginCache(string cacheDirectory) + { + foreach (var keyValuePair in _pluginBinaryStorages) + { + var key = keyValuePair.Key; + var currentCacheDirectory = key.Item2; + if (cacheDirectory == currentCacheDirectory) + { + _pluginBinaryStorages.Remove(key, out var _); + } + } + } + + /// + /// Save plugin caches. + /// + public void SavePluginCaches() + { + foreach (var value in _pluginBinaryStorages.Values) + { + var method = value.GetType().GetMethod("Save"); + method?.Invoke(value, null); + } + } + + public async Task LoadCacheBinaryStorageAsync(string cacheName, string cacheDirectory, T defaultData) where T : new() + { + var type = typeof(T); + if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type))) + _pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage(cacheName, cacheDirectory); + + return await ((PluginBinaryStorage)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).TryLoadAsync(defaultData); + } + + public async Task SaveCacheBinaryStorageAsync(string cacheName, string cacheDirectory) where T : new() + { + var type = typeof(T); + if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type))) + _pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage(cacheName, cacheDirectory); + + await ((PluginBinaryStorage)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).SaveAsync(); + } + #endregion #region Private Methods From 0496d6c04ac2bed323060f41519c41fe1358dc73 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 14:21:29 +0800 Subject: [PATCH 06/46] Use api functions for Program plugin --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 82 +++++++++---------- .../Programs/UWPPackage.cs | 2 +- .../Programs/Win32.cs | 2 +- .../Views/ProgramSetting.xaml.cs | 6 +- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 3be23214c..5bc518105 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -8,7 +8,6 @@ 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; @@ -19,19 +18,17 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program { - public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, - IDisposable + public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable { - internal static Win32[] _win32s { get; set; } - internal static UWPApp[] _uwps { get; set; } - internal static Settings _settings { get; set; } + private const string Win32CacheName = "Win32"; + private const string UwpCacheName = "UWP"; + internal static List _win32s { get; private set; } + internal static List _uwps { get; private set; } + internal static Settings _settings { get; private set; } internal static PluginInitContext Context { get; private set; } - private static BinaryStorage _win32Storage; - private static BinaryStorage _uwpStorage; - private static readonly List emptyResults = new(); private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 }; @@ -81,12 +78,6 @@ namespace Flow.Launcher.Plugin.Program { } - public void Save() - { - _win32Storage.SaveAsync(_win32s); - _uwpStorage.SaveAsync(_uwps); - } - public async Task> QueryAsync(Query query, CancellationToken token) { var result = await cache.GetOrCreateAsync(query.Search, async entry => @@ -191,7 +182,9 @@ namespace Flow.Launcher.Plugin.Program await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { - Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath); + var pluginCachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath; + + Helper.ValidateDirectory(pluginCachePath); static void MoveFile(string sourcePath, string destinationPath) { @@ -236,20 +229,18 @@ namespace Flow.Launcher.Plugin.Program } // 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"); + var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"{Win32CacheName}.cache"); + var newWin32CacheFile = Path.Combine(pluginCachePath, $"{Win32CacheName}.cache"); MoveFile(oldWin32CacheFile, newWin32CacheFile); - var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"UWP.cache"); - var newUWPCacheFile = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, $"UWP.cache"); + var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"{UwpCacheName}.cache"); + var newUWPCacheFile = Path.Combine(pluginCachePath, $"{UwpCacheName}.cache"); MoveFile(oldUWPCacheFile, newUWPCacheFile); - _win32Storage = new BinaryStorage("Win32", Context.CurrentPluginMetadata.PluginCacheDirectoryPath); - _win32s = await _win32Storage.TryLoadAsync(Array.Empty()); - _uwpStorage = new BinaryStorage("UWP", Context.CurrentPluginMetadata.PluginCacheDirectoryPath); - _uwps = await _uwpStorage.TryLoadAsync(Array.Empty()); + _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCachePath, new List()); + _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCachePath, new List()); }); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Count}>"); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Count}>"); bool cacheEmpty = !_win32s.Any() || !_uwps.Any(); @@ -273,36 +264,45 @@ namespace Flow.Launcher.Plugin.Program } } - public static void IndexWin32Programs() + public static async Task IndexWin32ProgramsAsync() { var win32S = Win32.All(_settings); - _win32s = win32S; + _win32s.Clear(); + foreach (var win32 in win32S) + { + _win32s.Add(win32); + } ResetCache(); - _win32Storage.SaveAsync(_win32s); + await Context.API.SaveCacheBinaryStorageAsync>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _settings.LastIndexTime = DateTime.Now; } - public static void IndexUwpPrograms() + public static async Task IndexUwpProgramsAsync() { - var applications = UWPPackage.All(_settings); - _uwps = applications; + var uwps = UWPPackage.All(_settings); + _uwps.Clear(); + foreach (var uwp in uwps) + { + _uwps.Add(uwp); + } ResetCache(); - _uwpStorage.SaveAsync(_uwps); + await Context.API.SaveCacheBinaryStorageAsync>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _settings.LastIndexTime = DateTime.Now; } public static async Task IndexProgramsAsync() { - var a = Task.Run(() => + var win32Task = Task.Run(async () => { - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); + await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32ProgramsAsync); }); - var b = Task.Run(() => + var uwpTask = Task.Run(async () => { - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpPrograms); + await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpProgramsAsync); }); - await Task.WhenAll(a, b).ConfigureAwait(false); + + await Task.WhenAll(win32Task, uwpTask).ConfigureAwait(false); } internal static void ResetCache() @@ -314,7 +314,7 @@ namespace Flow.Launcher.Plugin.Program public Control CreateSettingPanel() { - return new ProgramSetting(Context, _settings, _win32s, _uwps); + return new ProgramSetting(Context, _settings); } public string GetTranslatedPluginTitle() @@ -370,7 +370,7 @@ namespace Flow.Launcher.Plugin.Program _settings.DisabledProgramSources.Add(new ProgramSource(program)); _ = Task.Run(() => { - IndexUwpPrograms(); + _ = IndexUwpProgramsAsync(); }); } else if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) @@ -380,7 +380,7 @@ namespace Flow.Launcher.Plugin.Program _settings.DisabledProgramSources.Add(new ProgramSource(program)); _ = Task.Run(() => { - IndexWin32Programs(); + _ = IndexWin32ProgramsAsync(); }); } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs index 654897cc5..bf100ed7e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -317,7 +317,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { await Task.Delay(3000).ConfigureAwait(false); PackageChangeChannel.Reader.TryRead(out _); - await Task.Run(Main.IndexUwpPrograms); + await Task.Run(Main.IndexUwpProgramsAsync); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index a64a708ef..06be2a628 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -797,7 +797,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { } - await Task.Run(Main.IndexWin32Programs); + await Task.Run(Main.IndexWin32ProgramsAsync); } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs index 91864cb68..5ad7fcea3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs @@ -18,8 +18,8 @@ namespace Flow.Launcher.Plugin.Program.Views /// public partial class ProgramSetting : UserControl { - private PluginInitContext context; - private Settings _settings; + private readonly PluginInitContext context; + private readonly Settings _settings; private GridViewColumnHeader _lastHeaderClicked; private ListSortDirection _lastDirection; @@ -109,7 +109,7 @@ namespace Flow.Launcher.Plugin.Program.Views public bool ShowUWPCheckbox => UWPPackage.SupportUWP(); - public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWPApp[] uwps) + public ProgramSetting(PluginInitContext context, Settings settings) { this.context = context; _settings = settings; From 68e1fc28efcc31362193aab4e73aa7459bf1b1af Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 1 Apr 2025 14:35:43 +0800 Subject: [PATCH 07/46] Use try remove for safety --- Flow.Launcher/PublicAPIInstance.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 17d7e103e..770195550 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -201,7 +201,7 @@ namespace Flow.Launcher var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString(); if (name == assemblyName) { - _pluginJsonStorages.Remove(key, out var pluginJsonStorage); + _pluginJsonStorages.TryRemove(key, out var pluginJsonStorage); } } } @@ -344,7 +344,7 @@ namespace Flow.Launcher var currentCacheDirectory = key.Item2; if (cacheDirectory == currentCacheDirectory) { - _pluginBinaryStorages.Remove(key, out var _); + _pluginBinaryStorages.TryRemove(key, out var _); } } } From 53c12327c0bcce76b770273c6a95f2c15ed0d27b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 11:51:34 +0800 Subject: [PATCH 08/46] Revert changes --- Flow.Launcher.Infrastructure/Logger/Log.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 84331ef70..807d631c7 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -135,14 +135,12 @@ namespace Flow.Launcher.Infrastructure.Logger return className; } -#if !DEBUG private static void ExceptionInternal(string classAndMethod, string message, System.Exception e) { var logger = LogManager.GetLogger(classAndMethod); logger.Error(e, message); } -#endif private static void LogInternal(string message, LogLevel level) { From 5165ce8f2a5d11f3b81f3feb37887e03f4c6677f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:22:36 +0800 Subject: [PATCH 09/46] Add LoadImageAsync api function --- .../Image/ImageLoader.cs | 14 ++++++++------ Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 16 ++++++++++++++++ Flow.Launcher/PublicAPIInstance.cs | 4 ++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 6f7b1cd90..4bd4c29c8 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -277,7 +277,7 @@ namespace Flow.Launcher.Infrastructure.Image return ImageCache.TryGetValue(path, loadFullImage, out image); } - public static async ValueTask LoadAsync(string path, bool loadFullImage = false) + public static async ValueTask LoadAsync(string path, bool loadFullImage = false, bool cacheImage = true) { var imageResult = await LoadInternalAsync(path, loadFullImage); @@ -293,16 +293,18 @@ namespace Flow.Launcher.Infrastructure.Image // image already exists img = ImageCache[key, loadFullImage] ?? img; } - else + else if (cacheImage) { - // new guid - + // save guid key GuidToKey[hash] = path; } } - // update cache - ImageCache[path, loadFullImage] = img; + if (cacheImage) + { + // update cache + ImageCache[path, loadFullImage] = img; + } } return img; diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f178ebb90..fb80ede83 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Media; namespace Flow.Launcher.Plugin { @@ -344,5 +345,20 @@ namespace Flow.Launcher.Plugin /// Stop the loading bar in main window /// public void StopLoadingBar(); + + /// + /// Load image from path. Support local, remote and data:image url. + /// If image path is missing, it will retun a missing icon. + /// + /// The path of the image. + /// + /// Load full image or not. + /// + /// + /// Cache the image or not. Cached image will be stored in FL cache. + /// If the image is just used one time, it's better to set this to false. + /// + /// + ValueTask LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e19ad2fdc..d12c6ac3e 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Media; using CommunityToolkit.Mvvm.DependencyInjection; using Squirrel; using Flow.Launcher.Core; @@ -342,6 +343,9 @@ namespace Flow.Launcher public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); + public ValueTask LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) => + ImageLoader.LoadAsync(path, loadFullImage, cacheImage); + #endregion #region Private Methods From f76110856fdc1c104d3dc1c5a888073b1ac3f413 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:25:06 +0800 Subject: [PATCH 10/46] Use api function in main project --- Flow.Launcher/MessageBoxEx.xaml.cs | 2 +- Flow.Launcher/Msg.xaml.cs | 6 +++--- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- Flow.Launcher/ViewModel/PluginViewModel.cs | 2 +- Flow.Launcher/ViewModel/ResultViewModel.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/MessageBoxEx.xaml.cs b/Flow.Launcher/MessageBoxEx.xaml.cs index e9b434fd9..3d94769d0 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -156,7 +156,7 @@ namespace Flow.Launcher private async Task SetImageAsync(string imageName) { var imagePath = Path.Combine(Constant.ProgramDirectory, "Images", imageName); - var imageSource = await ImageLoader.LoadAsync(imagePath); + var imageSource = await App.API.LoadImageAsync(imagePath); Img.Source = imageSource; } diff --git a/Flow.Launcher/Msg.xaml.cs b/Flow.Launcher/Msg.xaml.cs index 94184ff63..ff9accd62 100644 --- a/Flow.Launcher/Msg.xaml.cs +++ b/Flow.Launcher/Msg.xaml.cs @@ -43,7 +43,7 @@ namespace Flow.Launcher private async System.Threading.Tasks.Task LoadImageAsync() { - imgClose.Source = await ImageLoader.LoadAsync(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png")); + imgClose.Source = await App.API.LoadImageAsync(Path.Combine(Constant.ProgramDirectory, "Images\\close.png")); } void imgClose_MouseUp(object sender, MouseButtonEventArgs e) @@ -71,11 +71,11 @@ namespace Flow.Launcher if (!File.Exists(iconPath)) { - imgIco.Source = await ImageLoader.LoadAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png")); + imgIco.Source = await App.API.LoadImageAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png")); } else { - imgIco.Source = await ImageLoader.LoadAsync(iconPath); + imgIco.Source = await App.API.LoadImageAsync(iconPath); } Show(); diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 17e4b55b7..bfa5bd504 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1168,7 +1168,7 @@ namespace Flow.Launcher.ViewModel else if (plugins.Count == 1) { PluginIconPath = plugins.Single().Metadata.IcoPath; - PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath); + PluginIconSource = await App.API.LoadImageAsync(PluginIconPath); SearchIconVisibility = Visibility.Hidden; } else diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index e91badb38..93a595de4 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -45,7 +45,7 @@ namespace Flow.Launcher.ViewModel private async Task LoadIconAsync() { - Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath); + Image = await App.API.LoadImageAsync(PluginPair.Metadata.IcoPath); OnPropertyChanged(nameof(Image)); } diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index db124e078..9aab71a32 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -199,7 +199,7 @@ namespace Flow.Launcher.ViewModel } } - return await ImageLoader.LoadAsync(imagePath, loadFullImage).ConfigureAwait(false); + return await App.API.LoadImageAsync(imagePath, loadFullImage).ConfigureAwait(false); } private async Task LoadImageAsync() From 4572068e768921e0834c53f6f4192c71ae762c04 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:25:28 +0800 Subject: [PATCH 11/46] Use api functions in plugin projects --- .../Views/PreviewPanel.xaml.cs | 3 +-- .../SearchSourceViewModel.cs | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs index 878832e4f..aaf1efdc1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs @@ -7,7 +7,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; -using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Plugin.Explorer.Search; namespace Flow.Launcher.Plugin.Explorer.Views; @@ -89,7 +88,7 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged private async Task LoadImageAsync() { - PreviewImage = await ImageLoader.LoadAsync(FilePath, true).ConfigureAwait(false); + PreviewImage = await Main.Context.API.LoadImageAsync(FilePath, true).ConfigureAwait(false); } public event PropertyChangedEventHandler? PropertyChanged; diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs index 9c5e81cb5..6554edd83 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceViewModel.cs @@ -1,5 +1,4 @@ -using Flow.Launcher.Infrastructure.Image; -using System; +using System; using System.IO; using System.Threading.Tasks; #pragma warning disable IDE0005 @@ -41,8 +40,8 @@ namespace Flow.Launcher.Plugin.WebSearch #if DEBUG throw; #else - Main._context.API.ShowMsgBox(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath)); - UpdateIconAttributes(selectedSearchSource, fullPathToOriginalImage); + Main._context.API.ShowMsgBox(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath)); + UpdateIconAttributes(selectedSearchSource, fullPathToOriginalImage); #endif } } @@ -61,7 +60,7 @@ namespace Flow.Launcher.Plugin.WebSearch internal async ValueTask LoadPreviewIconAsync(string pathToPreviewIconImage) { - return await ImageLoader.LoadAsync(pathToPreviewIconImage); + return await Main._context.API.LoadImageAsync(pathToPreviewIconImage); } } } From 3de8005297fcba6e517ea674e3b2dc1129aea8e8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:26:22 +0800 Subject: [PATCH 12/46] Improve WebSearch project code quality --- .../SuggestionSources/Baidu.cs | 8 +++----- .../SuggestionSources/Bing.cs | 16 +++++----------- .../SuggestionSources/DuckDuckGo.cs | 8 +++----- .../SuggestionSources/Google.cs | 8 +++----- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 51f81b718..590666af7 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -4,8 +4,6 @@ using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.Http; -using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; using System.Threading; @@ -22,11 +20,11 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources try { const string api = "http://suggestion.baidu.com/su?json=1&wd="; - result = await Http.GetAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false); + result = await Main._context.API.HttpGetStringAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false); } catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException}) { - Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); + Main._context.API.LogException(nameof(Baidu), "Can't get suggestion from baidu", e); return null; } @@ -41,7 +39,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (JsonException e) { - Log.Exception("|Baidu.Suggestions|can't parse suggestions", e); + Main._context.API.LogException(nameof(Baidu), "Can't parse suggestions", e); return new List(); } diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index 640674243..938f7d387 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -1,6 +1,4 @@ -using Flow.Launcher.Infrastructure.Http; -using Flow.Launcher.Infrastructure.Logger; -using System; +using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; @@ -10,16 +8,15 @@ using System.Threading; namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { - class Bing : SuggestionSource + public class Bing : SuggestionSource { public override async Task> SuggestionsAsync(string query, CancellationToken token) { - try { const string api = "https://api.bing.com/qsonhs.aspx?q="; - await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false); + await using var resultStream = await Main._context.API.HttpGetStreamAsync(api + Uri.EscapeDataString(query), token).ConfigureAwait(false); using var json = (await JsonDocument.ParseAsync(resultStream, cancellationToken: token)); var root = json.RootElement.GetProperty("AS"); @@ -33,18 +30,15 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources .EnumerateArray() .Select(s => s.GetProperty("Txt").GetString())) .ToList(); - - - } catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException }) { - Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); + Main._context.API.LogException(nameof(Bing), "Can't get suggestion from baidu", e); return null; } catch (JsonException e) { - Log.Exception("|Bing.Suggestions|can't parse suggestions", e); + Main._context.API.LogException(nameof(Bing), "Can't parse suggestions", e); return new List(); } } diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs index 8fafb44cc..1d248caf3 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.Http; -using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; using System.Threading; using System.Text.Json; @@ -25,7 +23,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { const string api = "https://duckduckgo.com/ac/?type=list&q="; - await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false); + await using var resultStream = await Main._context.API.HttpGetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false); using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token); @@ -36,12 +34,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException}) { - Log.Exception("|DuckDuckGo.Suggestions|Can't get suggestion from DuckDuckGo", e); + Main._context.API.LogException(nameof(DuckDuckGo), "Can't get suggestion from DuckDuckGo", e); return null; } catch (JsonException e) { - Log.Exception("|DuckDuckGo.Suggestions|can't parse suggestions", e); + Main._context.API.LogException(nameof(DuckDuckGo), "Can't parse suggestions", e); return new List(); } } diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 265de4a98..5f2504009 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.Http; -using Flow.Launcher.Infrastructure.Logger; using System.Net.Http; using System.Threading; using System.Text.Json; @@ -18,7 +16,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources { const string api = "https://www.google.com/complete/search?output=chrome&q="; - await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false); + await using var resultStream = await Main._context.API.HttpGetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false); using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token); @@ -29,12 +27,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException}) { - Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e); + Main._context.API.LogException(nameof(Google), "Can't get suggestion from baidu", e); return null; } catch (JsonException e) { - Log.Exception("|Google.Suggestions|can't parse suggestions", e); + Main._context.API.LogException(nameof(Google), "Can't parse suggestions", e); return new List(); } } From f9a01e10025563e069c3caafce85b80e89d60d2f Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:34:04 +0800 Subject: [PATCH 13/46] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index fb80ede83..0ee576b2e 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -348,7 +348,7 @@ namespace Flow.Launcher.Plugin /// /// Load image from path. Support local, remote and data:image url. - /// If image path is missing, it will retun a missing icon. + /// If image path is missing, it will return a missing icon. /// /// The path of the image. /// From 15a7e3a5afd8af4a9139935fd376d963963fb66b Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:35:03 +0800 Subject: [PATCH 14/46] Fix log typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs index 938f7d387..9efc36263 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs @@ -33,7 +33,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException }) { - Main._context.API.LogException(nameof(Bing), "Can't get suggestion from baidu", e); + Main._context.API.LogException(nameof(Bing), "Can't get suggestion from Bing", e); return null; } catch (JsonException e) From a33ecdbb971869605c4e8c03b5a0a00465040bd4 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:35:27 +0800 Subject: [PATCH 15/46] Fix log typos Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs index 5f2504009..ad8fb508f 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs @@ -27,7 +27,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException}) { - Main._context.API.LogException(nameof(Google), "Can't get suggestion from baidu", e); + Main._context.API.LogException(nameof(Google), "Can't get suggestion from Google", e); return null; } catch (JsonException e) From e6a8fe3523b7c810faebf2b2235e1322884c0696 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 18:36:25 +0800 Subject: [PATCH 16/46] Use caption letter --- .../Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs index 590666af7..65be06b53 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs @@ -24,7 +24,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources } catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException}) { - Main._context.API.LogException(nameof(Baidu), "Can't get suggestion from baidu", e); + Main._context.API.LogException(nameof(Baidu), "Can't get suggestion from Baidu", e); return null; } From 56536d018891a7a28e1c5916a1426045b3682360 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 16:45:58 +0800 Subject: [PATCH 17/46] Add log for plugin binary storage --- .../Storage/PluginBinaryStorage.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs index 87f51d5d7..d18060e3d 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs @@ -1,15 +1,49 @@ using System.IO; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Infrastructure.Storage { public class PluginBinaryStorage : BinaryStorage where T : new() { + private static readonly string ClassName = "PluginBinaryStorage"; + + // We should not initialize API in static constructor because it will create another API instance + private static IPublicAPI api = null; + private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); + public PluginBinaryStorage(string cacheName, string cacheDirectory) { DirectoryPath = cacheDirectory; - Helper.ValidateDirectory(DirectoryPath); + FilesFolders.ValidateDirectory(DirectoryPath); FilePath = Path.Combine(DirectoryPath, $"{cacheName}{FileSuffix}"); } + + public new void Save() + { + try + { + base.Save(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e); + } + } + + public new async Task SaveAsync() + { + try + { + await base.SaveAsync(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e); + } + } } } From 358b1fd7c875e4e4863227d53ee977872a9bd703 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 20:10:39 +0800 Subject: [PATCH 18/46] Move theme data and functions into api --- Flow.Launcher.Core/Resource/Theme.cs | 31 ++++---- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 19 +++++ Flow.Launcher.Plugin/ThemeData.cs | 71 +++++++++++++++++++ Flow.Launcher/PublicAPIInstance.cs | 13 ++++ 4 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 Flow.Launcher.Plugin/ThemeData.cs diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index e5980b62f..4cf9ce7a7 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -81,11 +81,6 @@ namespace Flow.Launcher.Core.Resource #region Theme Resources - public string GetCurrentTheme() - { - return _settings.Theme; - } - private void MakeSureThemeDirectoriesExist() { foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir))) @@ -127,7 +122,7 @@ namespace Flow.Launcher.Core.Resource try { // Load a ResourceDictionary for the specified theme. - var themeName = GetCurrentTheme(); + var themeName = _settings.Theme; var dict = GetThemeResourceDictionary(themeName); // Apply font settings to the theme resource. @@ -330,7 +325,7 @@ namespace Flow.Launcher.Core.Resource private ResourceDictionary GetCurrentResourceDictionary() { - return GetResourceDictionary(GetCurrentTheme()); + return GetResourceDictionary(_settings.Theme); } private ThemeData GetThemeDataFromPath(string path) @@ -383,9 +378,15 @@ namespace Flow.Launcher.Core.Resource #endregion - #region Load & Change + #region Get & Change Theme - public List LoadAvailableThemes() + public ThemeData GetCurrentTheme() + { + var themes = GetAvailableThemes(); + return themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme) ?? themes.FirstOrDefault(); + } + + public List GetAvailableThemes() { List themes = new List(); foreach (var themeDirectory in _themeDirectories) @@ -403,7 +404,7 @@ namespace Flow.Launcher.Core.Resource public bool ChangeTheme(string theme = null) { if (string.IsNullOrEmpty(theme)) - theme = GetCurrentTheme(); + theme = _settings.Theme; string path = GetThemePath(theme); try @@ -591,7 +592,7 @@ namespace Flow.Launcher.Core.Resource { AutoDropShadow(useDropShadowEffect); } - SetBlurForWindow(GetCurrentTheme(), backdropType); + SetBlurForWindow(_settings.Theme, backdropType); if (!BlurEnabled) { @@ -610,7 +611,7 @@ namespace Flow.Launcher.Core.Resource // Get the actual backdrop type and drop shadow effect settings var (backdropType, _) = GetActualValue(); - SetBlurForWindow(GetCurrentTheme(), backdropType); + SetBlurForWindow(_settings.Theme, backdropType); }, DispatcherPriority.Render); } @@ -898,11 +899,5 @@ namespace Flow.Launcher.Core.Resource } #endregion - - #region Classes - - public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null); - - #endregion } } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f178ebb90..bfcc2f9e0 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -344,5 +344,24 @@ namespace Flow.Launcher.Plugin /// Stop the loading bar in main window /// public void StopLoadingBar(); + + /// + /// Get all available themes + /// + /// + public List GetAvailableThemes(); + + /// + /// Get the current theme + /// + /// + public ThemeData GetCurrentTheme(); + + /// + /// Set the current theme + /// + /// + /// + public void SetCurrentTheme(ThemeData theme); } } diff --git a/Flow.Launcher.Plugin/ThemeData.cs b/Flow.Launcher.Plugin/ThemeData.cs new file mode 100644 index 000000000..d4a69ef09 --- /dev/null +++ b/Flow.Launcher.Plugin/ThemeData.cs @@ -0,0 +1,71 @@ +namespace Flow.Launcher.Plugin; + +/// +/// Theme data model +/// +public class ThemeData +{ + /// + /// Theme file name without extension + /// + public string FileNameWithoutExtension { get; private init; } + + /// + /// Theme name + /// + public string Name { get; private init; } + + /// + /// Theme file path + /// + public bool? IsDark { get; private init; } + + /// + /// Theme file path + /// + public bool? HasBlur { get; private init; } + + /// + /// Theme data constructor + /// + public ThemeData(string fileNameWithoutExtension, string name, bool? isDark = null, bool? hasBlur = null) + { + FileNameWithoutExtension = fileNameWithoutExtension; + Name = name; + IsDark = isDark; + HasBlur = hasBlur; + } + + /// + public static bool operator ==(ThemeData left, ThemeData right) + { + return left.Equals(right); + } + + /// + public static bool operator !=(ThemeData left, ThemeData right) + { + return !(left == right); + } + + /// + public override bool Equals(object obj) + { + if (obj is not ThemeData other) + return false; + return FileNameWithoutExtension == other.FileNameWithoutExtension && + Name == other.Name; + } + + /// + public override int GetHashCode() + { + return Name?.GetHashCode() ?? 0; + } + + /// + public override string ToString() + { + return Name; + } +} diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index d88eeb7c9..96ccb55c4 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -37,6 +37,9 @@ namespace Flow.Launcher private readonly Internationalization _translater; private readonly MainViewModel _mainVM; + private Theme _theme; + private Theme Theme => _theme ??= Ioc.Default.GetRequiredService(); + private readonly object _saveSettingsLock = new(); #region Constructor @@ -354,6 +357,16 @@ namespace Flow.Launcher public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); + public List GetAvailableThemes() => Theme.GetAvailableThemes(); + + public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme(); + + public void SetCurrentTheme(ThemeData theme) + { + Theme.ChangeTheme(theme.FileNameWithoutExtension); + _ = _theme.RefreshFrameAsync(); + } + #endregion #region Private Methods From 9b9704e938f1e74584c8bc7688bc333387700c6b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 20:11:01 +0800 Subject: [PATCH 19/46] Improve settings panel theme page & theme selector --- .../ViewModels/SettingsPaneThemeViewModel.cs | 14 +++--- .../Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 47 +++++-------------- 2 files changed, 17 insertions(+), 44 deletions(-) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 6e2488fe1..58cf3a314 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -28,25 +28,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; - private List _themes; - public List Themes => _themes ??= _theme.LoadAvailableThemes(); + private List _themes; + public List Themes => _themes ??= App.API.GetAvailableThemes(); - private Theme.ThemeData _selectedTheme; - public Theme.ThemeData SelectedTheme + private ThemeData _selectedTheme; + public ThemeData SelectedTheme { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme()); + get => _selectedTheme ??= Themes.Find(v => v == App.API.GetCurrentTheme()); set { _selectedTheme = value; - _theme.ChangeTheme(value.FileNameWithoutExtension); + App.API.SetCurrentTheme(value); // Update UI state OnPropertyChanged(nameof(BackdropType)); OnPropertyChanged(nameof(IsBackdropEnabled)); OnPropertyChanged(nameof(IsDropShadowEnabled)); OnPropertyChanged(nameof(DropShadowEffect)); - - _ = _theme.RefreshFrameAsync(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index 31faeba52..84bfa218e 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; using System.Linq; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Core.Resource; namespace Flow.Launcher.Plugin.Sys { @@ -11,32 +9,6 @@ namespace Flow.Launcher.Plugin.Sys private readonly PluginInitContext _context; - // Do not initialize it in the constructor, because it will cause null reference in - // var dicts = Application.Current.Resources.MergedDictionaries; line of Theme - private Theme theme = null; - private Theme Theme => theme ??= Ioc.Default.GetRequiredService(); - - #region Theme Selection - - // Theme select codes simplified from SettingsPaneThemeViewModel.cs - - private Theme.ThemeData _selectedTheme; - public Theme.ThemeData SelectedTheme - { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme()); - set - { - _selectedTheme = value; - Theme.ChangeTheme(value.FileNameWithoutExtension); - - _ = Theme.RefreshFrameAsync(); - } - } - - private List Themes => Theme.LoadAvailableThemes(); - - #endregion - public ThemeSelector(PluginInitContext context) { _context = context; @@ -44,28 +16,31 @@ namespace Flow.Launcher.Plugin.Sys public List Query(Query query) { + var themes = _context.API.GetAvailableThemes(); + var selectedTheme = _context.API.GetCurrentTheme(); + var search = query.SecondToEndSearch; if (string.IsNullOrWhiteSpace(search)) { - return Themes.Select(CreateThemeResult) + return themes.Select(x => CreateThemeResult(x, selectedTheme)) .OrderBy(x => x.Title) .ToList(); } - return Themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) + return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name))) .Where(x => x.matchResult.IsSearchPrecisionScoreMet()) - .Select(x => CreateThemeResult(x.theme, x.matchResult.Score, x.matchResult.MatchData)) + .Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData)) .OrderBy(x => x.Title) .ToList(); } - private Result CreateThemeResult(Theme.ThemeData theme) => CreateThemeResult(theme, 0, null); + private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null); - private Result CreateThemeResult(Theme.ThemeData theme, int score, IList highlightData) + private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) { - string themeName = theme.Name; + var themeName = theme.FileNameWithoutExtension; string title; - if (theme == SelectedTheme) + if (theme == selectedTheme) { title = $"{theme.Name} ★"; // Set current theme to the top @@ -101,7 +76,7 @@ namespace Flow.Launcher.Plugin.Sys Score = score, Action = c => { - SelectedTheme = theme; + _context.API.SetCurrentTheme(theme); _context.API.ReQuery(); return false; } From 1611ad37f37fde5ac054529cfa3a136e28fa3168 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 20:15:47 +0800 Subject: [PATCH 20/46] Fix ThemeData hashcode issue --- Flow.Launcher.Plugin/ThemeData.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/ThemeData.cs b/Flow.Launcher.Plugin/ThemeData.cs index d4a69ef09..6cbd0fe74 100644 --- a/Flow.Launcher.Plugin/ThemeData.cs +++ b/Flow.Launcher.Plugin/ThemeData.cs @@ -1,4 +1,6 @@ -namespace Flow.Launcher.Plugin; +using System; + +namespace Flow.Launcher.Plugin; /// /// Theme data model @@ -60,7 +62,7 @@ public class ThemeData /// public override int GetHashCode() { - return Name?.GetHashCode() ?? 0; + return HashCode.Combine(FileNameWithoutExtension, Name); } /// From 514fa0037a0f0ad6b78ad6ae17d943764cf696c7 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 20:23:07 +0800 Subject: [PATCH 21/46] Improve documents Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/ThemeData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/ThemeData.cs b/Flow.Launcher.Plugin/ThemeData.cs index 6cbd0fe74..90a45182c 100644 --- a/Flow.Launcher.Plugin/ThemeData.cs +++ b/Flow.Launcher.Plugin/ThemeData.cs @@ -23,7 +23,7 @@ public class ThemeData public bool? IsDark { get; private init; } /// - /// Theme file path + /// Indicates whether the theme supports blur effects /// public bool? HasBlur { get; private init; } From 28d92c789f181acb2060f7ff0b1c9f226bdb4905 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 20:23:36 +0800 Subject: [PATCH 22/46] Improve documents --- Flow.Launcher.Plugin/ThemeData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/ThemeData.cs b/Flow.Launcher.Plugin/ThemeData.cs index 90a45182c..1888be65e 100644 --- a/Flow.Launcher.Plugin/ThemeData.cs +++ b/Flow.Launcher.Plugin/ThemeData.cs @@ -18,7 +18,7 @@ public class ThemeData public string Name { get; private init; } /// - /// Theme file path + /// Indicates whether the theme supports dark mode /// public bool? IsDark { get; private init; } From 2e885ea5ccec4a42de61e1d1fe6ced1e319656ce Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 20:45:24 +0800 Subject: [PATCH 23/46] Remove useless variable --- Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index 84bfa218e..4b99efe3b 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -38,7 +38,6 @@ namespace Flow.Launcher.Plugin.Sys private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList highlightData) { - var themeName = theme.FileNameWithoutExtension; string title; if (theme == selectedTheme) { From e6d3d0fa9efba36feec5dbeb68e5e41e3af47039 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 11:11:14 +0800 Subject: [PATCH 24/46] Improve code quality --- .../Storage/BinaryStorage.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 81e2503ad..208e4bf11 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -81,13 +81,6 @@ namespace Flow.Launcher.Infrastructure.Storage } } - public async ValueTask SaveAsync() - { - await using var stream = new FileStream(FilePath, FileMode.Create); - await MemoryPackSerializer.SerializeAsync(stream, Data); - } - - // For SavePluginSettings function public void Save() { var serialized = MemoryPackSerializer.Serialize(Data); @@ -95,13 +88,20 @@ namespace Flow.Launcher.Infrastructure.Storage File.WriteAllBytes(FilePath, serialized); } - // ImageCache need to be converted into concurrent dictionary, so it does not need to cache loading results into Data + public async ValueTask SaveAsync() + { + await SaveAsync(Data.NonNull()); + } + + // ImageCache need to convert data into concurrent dictionary for usage, + // so we would better to clear the data public void ClearData() { Data = default; } - // ImageCache storages data in its class, so it needs to pass it to SaveAsync + // ImageCache storages data in its class, + // so we need to pass it to SaveAsync public async ValueTask SaveAsync(T data) { await using var stream = new FileStream(FilePath, FileMode.Create); From 6e5c7ad190a539903f068995bac117b15ca45cc3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 11:19:01 +0800 Subject: [PATCH 25/46] Use ISavable interface instead of reflection --- Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs | 3 ++- Flow.Launcher.Infrastructure/Storage/JsonStorage.cs | 8 +++++--- Flow.Launcher/PublicAPIInstance.cs | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 208e4bf11..f218c5d8d 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using MemoryPack; @@ -16,7 +17,7 @@ namespace Flow.Launcher.Infrastructure.Storage /// /// It utilize MemoryPack, which means the object must be MemoryPackSerializable /// - public class BinaryStorage + public class BinaryStorage : ISavable { protected T? Data; diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index a3488124b..cdf3ae909 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -1,18 +1,20 @@ -#nullable enable -using System; +using System; using System.Globalization; using System.IO; using System.Text.Json; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; +#nullable enable + namespace Flow.Launcher.Infrastructure.Storage { /// /// Serialize object using json format. /// - public class JsonStorage where T : new() + public class JsonStorage : ISavable where T : new() { protected T? Data; diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 99d4cf233..50052f1c9 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -225,8 +225,8 @@ namespace Flow.Launcher { foreach (var value in _pluginJsonStorages.Values) { - var method = value.GetType().GetMethod("Save"); - method?.Invoke(value, null); + var savable = value as ISavable; + savable?.Save(); } } @@ -368,8 +368,8 @@ namespace Flow.Launcher { foreach (var value in _pluginBinaryStorages.Values) { - var method = value.GetType().GetMethod("Save"); - method?.Invoke(value, null); + var savable = value as ISavable; + savable?.Save(); } } From 3185bda824ab19a132703d77dbf7c6844dd0a2a8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 11:31:24 +0800 Subject: [PATCH 26/46] Use IRemovable interface instead of reflection --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 ++++----- Flow.Launcher.Core/Storage/IRemovable.cs | 19 +++++++++++++++++++ Flow.Launcher/PublicAPIInstance.cs | 7 ++++--- 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 Flow.Launcher.Core/Storage/IRemovable.cs diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index c167a5b21..03ddcb13d 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -13,6 +13,7 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; +using IRemovable = Flow.Launcher.Core.Storage.IRemovable; using ISavable = Flow.Launcher.Plugin.ISavable; namespace Flow.Launcher.Core.Plugin @@ -588,12 +589,10 @@ namespace Flow.Launcher.Core.Plugin if (removePluginSettings) { // For dotnet plugins, we need to remove their PluginJsonStorage and PluginBinaryStorage instances - if (AllowedLanguage.IsDotNet(plugin.Language)) + if (AllowedLanguage.IsDotNet(plugin.Language) && API is IRemovable removable) { - var method = API.GetType().GetMethod("RemovePluginSettings"); - method?.Invoke(API, new object[] { plugin.AssemblyName }); - var method1 = API.GetType().GetMethod("RemovePluginCache"); - method1?.Invoke(API, new object[] { plugin.PluginCacheDirectoryPath }); + removable.RemovePluginSettings(plugin.AssemblyName); + removable.RemovePluginCaches(plugin.PluginCacheDirectoryPath); } try diff --git a/Flow.Launcher.Core/Storage/IRemovable.cs b/Flow.Launcher.Core/Storage/IRemovable.cs new file mode 100644 index 000000000..fc34395e0 --- /dev/null +++ b/Flow.Launcher.Core/Storage/IRemovable.cs @@ -0,0 +1,19 @@ +namespace Flow.Launcher.Core.Storage; + +/// +/// Remove storage instances from instance +/// +public interface IRemovable +{ + /// + /// Remove all instances of one plugin + /// + /// + public void RemovePluginSettings(string assemblyName); + + /// + /// Remove all instances of one plugin + /// + /// + public void RemovePluginCaches(string cacheDirectory); +} diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 50052f1c9..b34006272 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -14,6 +14,8 @@ using CommunityToolkit.Mvvm.DependencyInjection; using Squirrel; using Flow.Launcher.Core; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Resource; +using Flow.Launcher.Core.Storage; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; @@ -27,11 +29,10 @@ using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.ViewModel; using JetBrains.Annotations; -using Flow.Launcher.Core.Resource; namespace Flow.Launcher { - public class PublicAPIInstance : IPublicAPI + public class PublicAPIInstance : IPublicAPI, IRemovable { private readonly Settings _settings; private readonly Internationalization _translater; @@ -348,7 +349,7 @@ namespace Flow.Launcher private readonly ConcurrentDictionary<(string, string, Type), object> _pluginBinaryStorages = new(); - public void RemovePluginCache(string cacheDirectory) + public void RemovePluginCaches(string cacheDirectory) { foreach (var keyValuePair in _pluginBinaryStorages) { From ecd019dd6b0c286a6cf223cf598bc1fc57dd4279 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 15:56:54 +0800 Subject: [PATCH 27/46] Move ThemeData class to SharedModels --- Flow.Launcher.Core/Resource/Theme.cs | 1 + Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 6 +++--- Flow.Launcher.Plugin/{ => SharedModels}/ThemeData.cs | 2 +- .../SettingPages/ViewModels/SettingsPaneThemeViewModel.cs | 1 + Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) rename Flow.Launcher.Plugin/{ => SharedModels}/ThemeData.cs (97%) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 4cf9ce7a7..f3eba7ba7 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -16,6 +16,7 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModels; using Microsoft.Win32; namespace Flow.Launcher.Core.Resource diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 64bdcec34..1090a3a1e 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -1,6 +1,4 @@ -using Flow.Launcher.Plugin.SharedModels; -using JetBrains.Annotations; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -8,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; +using Flow.Launcher.Plugin.SharedModels; +using JetBrains.Annotations; namespace Flow.Launcher.Plugin { diff --git a/Flow.Launcher.Plugin/ThemeData.cs b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs similarity index 97% rename from Flow.Launcher.Plugin/ThemeData.cs rename to Flow.Launcher.Plugin/SharedModels/ThemeData.cs index 1888be65e..6a5e54f55 100644 --- a/Flow.Launcher.Plugin/ThemeData.cs +++ b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs @@ -1,6 +1,6 @@ using System; -namespace Flow.Launcher.Plugin; +namespace Flow.Launcher.Plugin.SharedModels; /// /// Theme data model diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 58cf3a314..f78704ef2 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -12,6 +12,7 @@ using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.ViewModel; using ModernWpf; using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager; diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index 4b99efe3b..feacc3f99 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.Sys { From 6c458828bc9d24a58de29240f1417c253c64a7d6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:01:46 +0800 Subject: [PATCH 28/46] Fix possible NullReferenceException --- Flow.Launcher.Plugin/SharedModels/ThemeData.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs index 6a5e54f55..322985f3a 100644 --- a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs +++ b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs @@ -41,12 +41,16 @@ public class ThemeData /// public static bool operator ==(ThemeData left, ThemeData right) { + if (left is null && right is null) + return true; return left.Equals(right); } /// public static bool operator !=(ThemeData left, ThemeData right) { + if (left is null && right is null) + return false; return !(left == right); } From 6c5bb7d184d1f5a1284f94647e6f34ce1f0b2b84 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 8 Apr 2025 18:12:10 +1000 Subject: [PATCH 29/46] update summary --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 30004952e..111bc716c 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -228,7 +228,7 @@ namespace Flow.Launcher.Plugin void LogWarn(string className, string message, [CallerMemberName] string methodName = ""); /// - /// Log error message + /// Log error message. Preferred error logging method for plugins. /// void LogError(string className, string message, [CallerMemberName] string methodName = ""); From 537c03f2d751345176bbeacf1d58a657da3aef31 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:13:58 +0800 Subject: [PATCH 30/46] Fix null check issue Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/SharedModels/ThemeData.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs index 322985f3a..093e1ee8e 100644 --- a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs +++ b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs @@ -43,6 +43,8 @@ public class ThemeData { if (left is null && right is null) return true; + if (left is null || right is null) + return false; return left.Equals(right); } From b3aa89773ce1fdfadc9dfd06b13acc596d504265 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:14:17 +0800 Subject: [PATCH 31/46] Use == for != operator Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/SharedModels/ThemeData.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs index 093e1ee8e..cb389c21f 100644 --- a/Flow.Launcher.Plugin/SharedModels/ThemeData.cs +++ b/Flow.Launcher.Plugin/SharedModels/ThemeData.cs @@ -51,8 +51,6 @@ public class ThemeData /// public static bool operator !=(ThemeData left, ThemeData right) { - if (left is null && right is null) - return false; return !(left == right); } From a2d99573855b145a4ac68f270494c05f07ea06a6 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:20:11 +0800 Subject: [PATCH 32/46] Log warning if cannot find matched theme Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Core/Resource/Theme.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index f3eba7ba7..d7c619330 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -384,7 +384,12 @@ namespace Flow.Launcher.Core.Resource public ThemeData GetCurrentTheme() { var themes = GetAvailableThemes(); - return themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme) ?? themes.FirstOrDefault(); + var matchingTheme = themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme); + if (matchingTheme == null) + { + Log.Warn($"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme."); + } + return matchingTheme ?? themes.FirstOrDefault(); } public List GetAvailableThemes() From 7da2884e84ebd45dc70c16cd3dde6f6ef1e2b4af Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:29:03 +0800 Subject: [PATCH 33/46] Add locks for win32s & uwps --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 61 +++++++++++++++---- .../Views/Commands/ProgramSettingDisplay.cs | 34 ++++++++--- .../Views/ProgramSetting.xaml.cs | 18 +++--- 3 files changed, 83 insertions(+), 30 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index d3c50b406..6d2ae70fc 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -27,6 +27,9 @@ namespace Flow.Launcher.Plugin.Program internal static List _uwps { get; private set; } internal static Settings _settings { get; private set; } + internal static SemaphoreSlim _win32sLock = new(1, 1); + internal static SemaphoreSlim _uwpsLock = new(1, 1); + internal static PluginInitContext Context { get; private set; } private static readonly List emptyResults = new(); @@ -82,8 +85,11 @@ namespace Flow.Launcher.Plugin.Program { var result = await cache.GetOrCreateAsync(query.Search, async entry => { - var resultList = await Task.Run(() => + var resultList = await Task.Run(async () => { + await _win32sLock.WaitAsync(token); + await _uwpsLock.WaitAsync(token); + try { // Collect all UWP Windows app directories @@ -95,22 +101,26 @@ namespace Flow.Launcher.Plugin.Program .ToArray() : null; return _win32s.Cast() - .Concat(_uwps) - .AsParallel() - .WithCancellation(token) - .Where(HideUninstallersFilter) - .Where(p => HideDuplicatedWindowsAppFilter(p, uwpsDirectories)) - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, Context.API)) - .Where(r => r?.Score > 0) - .ToList(); + .Concat(_uwps) + .AsParallel() + .WithCancellation(token) + .Where(HideUninstallersFilter) + .Where(p => HideDuplicatedWindowsAppFilter(p, uwpsDirectories)) + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, Context.API)) + .Where(r => r?.Score > 0) + .ToList(); } catch (OperationCanceledException) { Log.Debug("|Flow.Launcher.Plugin.Program.Main|Query operation cancelled"); return emptyResults; } - + finally + { + _uwpsLock.Release(); + _win32sLock.Release(); + } }, token); resultList = resultList.Any() ? resultList : emptyResults; @@ -236,14 +246,25 @@ namespace Flow.Launcher.Plugin.Program var newUWPCacheFile = Path.Combine(pluginCachePath, $"{UwpCacheName}.cache"); MoveFile(oldUWPCacheFile, newUWPCacheFile); + await _win32sLock.WaitAsync(); _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCachePath, new List()); + _win32sLock.Release(); + + await _uwpsLock.WaitAsync(); _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCachePath, new List()); + _uwpsLock.Release(); }); + await _win32sLock.WaitAsync(); + await _uwpsLock.WaitAsync(); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Count}>"); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Count}>"); bool cacheEmpty = !_win32s.Any() || !_uwps.Any(); + _win32sLock.Release(); + _uwpsLock.Release(); + if (cacheEmpty || _settings.LastIndexTime.AddHours(30) < DateTime.Now) { _ = Task.Run(async () => @@ -267,11 +288,13 @@ namespace Flow.Launcher.Plugin.Program public static async Task IndexWin32ProgramsAsync() { var win32S = Win32.All(_settings); + await _win32sLock.WaitAsync(); _win32s.Clear(); foreach (var win32 in win32S) { _win32s.Add(win32); } + _win32sLock.Release(); ResetCache(); await Context.API.SaveCacheBinaryStorageAsync>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _settings.LastIndexTime = DateTime.Now; @@ -280,11 +303,13 @@ namespace Flow.Launcher.Plugin.Program public static async Task IndexUwpProgramsAsync() { var uwps = UWPPackage.All(_settings); + await _uwpsLock.WaitAsync(); _uwps.Clear(); foreach (var uwp in uwps) { _uwps.Add(uwp); } + _uwpsLock.Release(); ResetCache(); await Context.API.SaveCacheBinaryStorageAsync>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _settings.LastIndexTime = DateTime.Now; @@ -358,26 +383,36 @@ namespace Flow.Launcher.Plugin.Program return menuOptions; } - private static void DisableProgram(IProgram programToDelete) + private static async Task DisableProgram(IProgram programToDelete) { if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) return; + await _uwpsLock.WaitAsync(); if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) { var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier); program.Enabled = false; _settings.DisabledProgramSources.Add(new ProgramSource(program)); + _uwpsLock.Release(); + + // Reindex UWP programs _ = Task.Run(() => { _ = IndexUwpProgramsAsync(); }); + return; } - else if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) + + await _win32sLock.WaitAsync(); + if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) { var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier); program.Enabled = false; _settings.DisabledProgramSources.Add(new ProgramSource(program)); + _win32sLock.Release(); + + // Reindex Win32 programs _ = Task.Run(() => { _ = IndexWin32ProgramsAsync(); diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs index e4d7c323a..b89a2a6ba 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Flow.Launcher.Plugin.Program.Views.Models; namespace Flow.Launcher.Plugin.Program.Views.Commands @@ -15,21 +16,24 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands .ToList(); } - internal static void DisplayAllPrograms() + internal static async Task DisplayAllProgramsAsync() { + await Main._win32sLock.WaitAsync(); var win32 = Main._win32s .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Select(x => new ProgramSource(x)); + ProgramSetting.ProgramSettingDisplayList.AddRange(win32); + Main._win32sLock.Release(); + await Main._uwpsLock.WaitAsync(); var uwp = Main._uwps .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Select(x => new ProgramSource(x)); - - ProgramSetting.ProgramSettingDisplayList.AddRange(win32); ProgramSetting.ProgramSettingDisplayList.AddRange(uwp); + Main._uwpsLock.Release(); } - internal static void SetProgramSourcesStatus(List selectedProgramSourcesToDisable, bool status) + internal static async Task SetProgramSourcesStatusAsync(List selectedProgramSourcesToDisable, bool status) { foreach(var program in ProgramSetting.ProgramSettingDisplayList) { @@ -39,14 +43,17 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands } } - foreach(var program in Main._win32s) + await Main._win32sLock.WaitAsync(); + foreach (var program in Main._win32s) { if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status)) { program.Enabled = status; } } + Main._win32sLock.Release(); + await Main._uwpsLock.WaitAsync(); foreach (var program in Main._uwps) { if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status)) @@ -54,6 +61,7 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands program.Enabled = status; } } + Main._uwpsLock.Release(); } internal static void StoreDisabledInSettings() @@ -72,12 +80,22 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled); } - internal static bool IsReindexRequired(this List selectedItems) + internal static async Task IsReindexRequiredAsync(this List selectedItems) { // Not in cache - if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)) + await Main._win32sLock.WaitAsync(); + await Main._uwpsLock.WaitAsync(); + try + { + if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)) && selectedItems.Any(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))) - return true; + return true; + } + finally + { + Main._win32sLock.Release(); + Main._uwpsLock.Release(); + } // ProgramSources holds list of user added directories, // so when we enable/disable we need to reindex to show/not show the programs diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs index 5ad7fcea3..c42bd4f30 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs @@ -183,7 +183,7 @@ namespace Flow.Launcher.Plugin.Program.Views EditProgramSource(selectedProgramSource); } - private void EditProgramSource(ProgramSource selectedProgramSource) + private async void EditProgramSource(ProgramSource selectedProgramSource) { if (selectedProgramSource == null) { @@ -202,13 +202,13 @@ namespace Flow.Launcher.Plugin.Program.Views { if (selectedProgramSource.Enabled) { - ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, + await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List { selectedProgramSource }, true); // sync status in win32, uwp and disabled ProgramSettingDisplay.RemoveDisabledFromSettings(); } else { - ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, + await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List { selectedProgramSource }, false); ProgramSettingDisplay.StoreDisabledInSettings(); } @@ -277,14 +277,14 @@ namespace Flow.Launcher.Plugin.Program.Views } } - private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e) + private async void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e) { - ProgramSettingDisplay.DisplayAllPrograms(); + await ProgramSettingDisplay.DisplayAllProgramsAsync(); ViewRefresh(); } - private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e) + private async void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e) { var selectedItems = programSourceView .SelectedItems.Cast() @@ -311,18 +311,18 @@ namespace Flow.Launcher.Plugin.Program.Views } else if (HasMoreOrEqualEnabledItems(selectedItems)) { - ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, false); + await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, false); ProgramSettingDisplay.StoreDisabledInSettings(); } else { - ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, true); + await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, true); ProgramSettingDisplay.RemoveDisabledFromSettings(); } - if (selectedItems.IsReindexRequired()) + if (await selectedItems.IsReindexRequiredAsync()) ReIndexing(); programSourceView.SelectedItems.Clear(); From dd210ad41968a1e9112fb6b364a08093534ae136 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:41:06 +0800 Subject: [PATCH 34/46] Remove RefreshFrameAsync since ChangeTheme calls this function --- Flow.Launcher.Core/Resource/Theme.cs | 2 +- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 6 ++++-- Flow.Launcher/PublicAPIInstance.cs | 5 +---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index d7c619330..59e76e2d2 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -433,7 +433,7 @@ namespace Flow.Launcher.Core.Resource BlurEnabled = IsBlurTheme(); - // Can only apply blur but here also apply drop shadow effect to avoid possible drop shadow effect issues + // Apply blur and drop shadow effect so that we do not need to call it again _ = RefreshFrameAsync(); return true; diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 056c4a437..7701fcdd0 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -367,8 +367,10 @@ namespace Flow.Launcher.Plugin /// Set the current theme /// /// - /// - public void SetCurrentTheme(ThemeData theme); + /// + /// True if the theme is set successfully, false otherwise. + /// + public bool SetCurrentTheme(ThemeData theme); /// Load image from path. Support local, remote and data:image url. /// If image path is missing, it will return a missing icon. diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index b19b21db1..8cce460d7 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -377,11 +377,8 @@ namespace Flow.Launcher public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme(); - public void SetCurrentTheme(ThemeData theme) - { + public bool SetCurrentTheme(ThemeData theme) => Theme.ChangeTheme(theme.FileNameWithoutExtension); - _ = _theme.RefreshFrameAsync(); - } public ValueTask LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) => ImageLoader.LoadAsync(path, loadFullImage, cacheImage); From a3c7be95597f8c0765f0e62749b7f263749e78cd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:49:29 +0800 Subject: [PATCH 35/46] Handle results from SetCurrentTheme --- Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index feacc3f99..f8aeaeafd 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -76,8 +76,10 @@ namespace Flow.Launcher.Plugin.Sys Score = score, Action = c => { - _context.API.SetCurrentTheme(theme); - _context.API.ReQuery(); + if (_context.API.SetCurrentTheme(theme)) + { + _context.API.ReQuery(); + } return false; } }; From 734c5bb67deb769190fa46572083e64ffcef8cf8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 16:59:27 +0800 Subject: [PATCH 36/46] Fix lock release issue --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 6d2ae70fc..a5fbc6c70 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -367,7 +367,7 @@ namespace Flow.Launcher.Plugin.Program Title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_program"), Action = c => { - DisableProgram(program); + _ = DisableProgramAsync(program); Context.API.ShowMsg( Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"), Context.API.GetTranslation( @@ -383,7 +383,7 @@ namespace Flow.Launcher.Plugin.Program return menuOptions; } - private static async Task DisableProgram(IProgram programToDelete) + private static async Task DisableProgramAsync(IProgram programToDelete) { if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) return; @@ -403,7 +403,12 @@ namespace Flow.Launcher.Plugin.Program }); return; } - + else + { + // Release the lock if we cannot find the program + _uwpsLock.Release(); + } + await _win32sLock.WaitAsync(); if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) { @@ -418,6 +423,11 @@ namespace Flow.Launcher.Plugin.Program _ = IndexWin32ProgramsAsync(); }); } + else + { + // Release the lock if we cannot find the program + _win32sLock.Release(); + } } public static void StartProcess(Func runProcess, ProcessStartInfo info) From c11ee2f9e78fa626bd7801fed293d990fdfaf682 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 17:20:57 +0800 Subject: [PATCH 37/46] Improve code quality & comments & Fix lock issue --- .../Storage/BinaryStorage.cs | 6 ++-- .../Storage/PluginJsonStorage.cs | 1 + Flow.Launcher.Plugin/Interfaces/ISavable.cs | 9 ++++-- Flow.Launcher/PublicAPIInstance.cs | 6 ---- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 29 +++++++------------ 5 files changed, 20 insertions(+), 31 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index f218c5d8d..b5de3b50f 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -42,8 +42,7 @@ namespace Flow.Launcher.Infrastructure.Storage public async ValueTask TryLoadAsync(T defaultData) { - if (Data != null) - return Data; + if (Data != null) return Data; if (File.Exists(FilePath)) { @@ -55,8 +54,7 @@ namespace Flow.Launcher.Infrastructure.Storage } await using var stream = new FileStream(FilePath, FileMode.Open); - var d = await DeserializeAsync(stream, defaultData); - Data = d; + Data = await DeserializeAsync(stream, defaultData); } else { diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index b63e8c1ef..e8cbd70fb 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -20,6 +20,7 @@ namespace Flow.Launcher.Infrastructure.Storage public PluginJsonStorage() { + // C# related, add python related below var dataType = typeof(T); AssemblyName = dataType.Assembly.GetName().Name; DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName); diff --git a/Flow.Launcher.Plugin/Interfaces/ISavable.cs b/Flow.Launcher.Plugin/Interfaces/ISavable.cs index cabd26962..0d23c0fc8 100644 --- a/Flow.Launcher.Plugin/Interfaces/ISavable.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISavable.cs @@ -1,18 +1,21 @@ namespace Flow.Launcher.Plugin { /// - /// Inherit this interface if additional data e.g. cache needs to be saved. + /// Inherit this interface if additional data. + /// If you need to save data which is not a setting or cache, + /// please implement this interface. /// /// /// For storing plugin settings, prefer /// or . + /// For storing plugin caches, prefer /// or . - /// Once called, your settings will be automatically saved by Flow. + /// Once called, those settings and caches will be automatically saved by Flow. /// public interface ISavable : IFeatures { /// - /// Save additional plugin data, such as cache. + /// Save additional plugin data. /// void Save(); } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 4dd0268d2..a523f90bb 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -229,9 +229,6 @@ namespace Flow.Launcher } } - /// - /// Save plugin settings. - /// public void SavePluginSettings() { foreach (var value in _pluginJsonStorages.Values) @@ -378,9 +375,6 @@ namespace Flow.Launcher } } - /// - /// Save plugin caches. - /// public void SavePluginCaches() { foreach (var value in _pluginBinaryStorages.Values) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index a5fbc6c70..561044981 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -77,10 +77,6 @@ namespace Flow.Launcher.Plugin.Program private static readonly string WindowsAppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps"); - static Main() - { - } - public async Task> QueryAsync(Query query, CancellationToken token) { var result = await cache.GetOrCreateAsync(query.Search, async entry => @@ -101,15 +97,15 @@ namespace Flow.Launcher.Plugin.Program .ToArray() : null; return _win32s.Cast() - .Concat(_uwps) - .AsParallel() - .WithCancellation(token) - .Where(HideUninstallersFilter) - .Where(p => HideDuplicatedWindowsAppFilter(p, uwpsDirectories)) - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, Context.API)) - .Where(r => r?.Score > 0) - .ToList(); + .Concat(_uwps) + .AsParallel() + .WithCancellation(token) + .Where(HideUninstallersFilter) + .Where(p => HideDuplicatedWindowsAppFilter(p, uwpsDirectories)) + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, Context.API)) + .Where(r => r?.Score > 0) + .ToList(); } catch (OperationCanceledException) { @@ -193,7 +189,6 @@ namespace Flow.Launcher.Plugin.Program await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { var pluginCachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath; - FilesFolders.ValidateDirectory(pluginCachePath); static void MoveFile(string sourcePath, string destinationPath) @@ -294,10 +289,10 @@ namespace Flow.Launcher.Plugin.Program { _win32s.Add(win32); } - _win32sLock.Release(); ResetCache(); await Context.API.SaveCacheBinaryStorageAsync>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _settings.LastIndexTime = DateTime.Now; + _win32sLock.Release(); } public static async Task IndexUwpProgramsAsync() @@ -309,10 +304,10 @@ namespace Flow.Launcher.Plugin.Program { _uwps.Add(uwp); } - _uwpsLock.Release(); ResetCache(); await Context.API.SaveCacheBinaryStorageAsync>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); _settings.LastIndexTime = DateTime.Now; + _uwpsLock.Release(); } public static async Task IndexProgramsAsync() @@ -405,7 +400,6 @@ namespace Flow.Launcher.Plugin.Program } else { - // Release the lock if we cannot find the program _uwpsLock.Release(); } @@ -425,7 +419,6 @@ namespace Flow.Launcher.Plugin.Program } else { - // Release the lock if we cannot find the program _win32sLock.Release(); } } From 54e7652084245900e06c4513a1c6926ae13cf3b3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 17:22:32 +0800 Subject: [PATCH 38/46] Fix see cref issue --- Flow.Launcher.Core/Storage/IRemovable.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Storage/IRemovable.cs b/Flow.Launcher.Core/Storage/IRemovable.cs index fc34395e0..bcf1cdd5e 100644 --- a/Flow.Launcher.Core/Storage/IRemovable.cs +++ b/Flow.Launcher.Core/Storage/IRemovable.cs @@ -1,18 +1,18 @@ namespace Flow.Launcher.Core.Storage; /// -/// Remove storage instances from instance +/// Remove storage instances from instance /// public interface IRemovable { /// - /// Remove all instances of one plugin + /// Remove all instances of one plugin /// /// public void RemovePluginSettings(string assemblyName); /// - /// Remove all instances of one plugin + /// Remove all instances of one plugin /// /// public void RemovePluginCaches(string cacheDirectory); From 68268026de3261ea5eb7a28ff09f943adc816d0c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:21:03 +0800 Subject: [PATCH 39/46] Improve performance --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 561044981..c235fb587 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -186,6 +186,8 @@ namespace Flow.Launcher.Plugin.Program _settings = context.API.LoadSettingJsonStorage(); + var _win32sCount = 0; + var _uwpsCount = 0; await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { var pluginCachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath; @@ -243,19 +245,18 @@ namespace Flow.Launcher.Plugin.Program await _win32sLock.WaitAsync(); _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCachePath, new List()); + _win32sCount = _win32s.Count; _win32sLock.Release(); await _uwpsLock.WaitAsync(); _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCachePath, new List()); + _uwpsCount = _uwps.Count; _uwpsLock.Release(); }); - await _win32sLock.WaitAsync(); - await _uwpsLock.WaitAsync(); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32sCount}>"); + Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwpsCount}>"); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Count}>"); - Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Count}>"); - - bool cacheEmpty = !_win32s.Any() || !_uwps.Any(); + var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0; _win32sLock.Release(); _uwpsLock.Release(); From 4c4a6c0e22f65a7c1fd0865c70ad69b86e8a0aea Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:27:53 +0800 Subject: [PATCH 40/46] Add log handler for indexing --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 52 ++++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index c235fb587..11deb710d 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -283,32 +283,52 @@ namespace Flow.Launcher.Plugin.Program public static async Task IndexWin32ProgramsAsync() { - var win32S = Win32.All(_settings); await _win32sLock.WaitAsync(); - _win32s.Clear(); - foreach (var win32 in win32S) + try { - _win32s.Add(win32); + var win32S = Win32.All(_settings); + _win32s.Clear(); + foreach (var win32 in win32S) + { + _win32s.Add(win32); + } + ResetCache(); + await Context.API.SaveCacheBinaryStorageAsync>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); + _settings.LastIndexTime = DateTime.Now; + } + catch (Exception e) + { + Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Win32 programs", e); + } + finally + { + _win32sLock.Release(); } - ResetCache(); - await Context.API.SaveCacheBinaryStorageAsync>(Win32CacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); - _settings.LastIndexTime = DateTime.Now; - _win32sLock.Release(); } public static async Task IndexUwpProgramsAsync() { - var uwps = UWPPackage.All(_settings); await _uwpsLock.WaitAsync(); - _uwps.Clear(); - foreach (var uwp in uwps) + try { - _uwps.Add(uwp); + var uwps = UWPPackage.All(_settings); + _uwps.Clear(); + foreach (var uwp in uwps) + { + _uwps.Add(uwp); + } + ResetCache(); + await Context.API.SaveCacheBinaryStorageAsync>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); + _settings.LastIndexTime = DateTime.Now; + } + catch (Exception e) + { + Log.Exception("|Flow.Launcher.Plugin.Program.Main|Failed to index Uwp programs", e); + } + finally + { + _uwpsLock.Release(); } - ResetCache(); - await Context.API.SaveCacheBinaryStorageAsync>(UwpCacheName, Context.CurrentPluginMetadata.PluginCacheDirectoryPath); - _settings.LastIndexTime = DateTime.Now; - _uwpsLock.Release(); } public static async Task IndexProgramsAsync() From aaadf167777a9374f07a523a11b3c102c403ee5b Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:28:29 +0800 Subject: [PATCH 41/46] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 2f8672e13..85c48f8a4 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -366,7 +366,7 @@ namespace Flow.Launcher.Plugin /// Default data to return /// /// - /// BinaryStorage utilize MemoryPack, which means the object must be MemoryPackSerializable + /// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable /// Task LoadCacheBinaryStorageAsync(string cacheName, string cacheDirectory, T defaultData) where T : new(); From 4749ca208abe9dbcfeb4f80470e2e6e489300e12 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:33:48 +0800 Subject: [PATCH 42/46] Improve code comments --- Flow.Launcher.Plugin/Interfaces/ISavable.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/Interfaces/ISavable.cs b/Flow.Launcher.Plugin/Interfaces/ISavable.cs index 0d23c0fc8..38cbf8e08 100644 --- a/Flow.Launcher.Plugin/Interfaces/ISavable.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISavable.cs @@ -1,8 +1,7 @@ namespace Flow.Launcher.Plugin { /// - /// Inherit this interface if additional data. - /// If you need to save data which is not a setting or cache, + /// Inherit this interface if you need to save additional data which is not a setting or cache, /// please implement this interface. /// /// From 2ff09cf9b0acb61e3323c80481e6eac5e2873891 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:41:02 +0800 Subject: [PATCH 43/46] Improve code quality --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 11deb710d..107673f89 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -413,10 +413,7 @@ namespace Flow.Launcher.Plugin.Program _uwpsLock.Release(); // Reindex UWP programs - _ = Task.Run(() => - { - _ = IndexUwpProgramsAsync(); - }); + _ = Task.Run(IndexUwpProgramsAsync); return; } else @@ -433,10 +430,8 @@ namespace Flow.Launcher.Plugin.Program _win32sLock.Release(); // Reindex Win32 programs - _ = Task.Run(() => - { - _ = IndexWin32ProgramsAsync(); - }); + _ = Task.Run(IndexWin32ProgramsAsync); + return; } else { From 653b8335700290f175b7f9bce369a849d0d48f81 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:42:59 +0800 Subject: [PATCH 44/46] Fix typos --- Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs | 2 +- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index b5de3b50f..43bb8dade 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -15,7 +15,7 @@ namespace Flow.Launcher.Infrastructure.Storage /// Normally, it has better performance, but not readable /// /// - /// It utilize MemoryPack, which means the object must be MemoryPackSerializable + /// It utilizes MemoryPack, which means the object must be MemoryPackSerializable /// public class BinaryStorage : ISavable { diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 85c48f8a4..a3020b607 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -380,7 +380,7 @@ namespace Flow.Launcher.Plugin /// Cache directory from plugin metadata /// /// - /// BinaryStorage utilize MemoryPack, which means the object must be MemoryPackSerializable + /// BinaryStorage utilizes MemoryPack, which means the object must be MemoryPackSerializable /// Task SaveCacheBinaryStorageAsync(string cacheName, string cacheDirectory) where T : new(); From d7ca36e60a39892295fe2c42c9e2f81e56b59a66 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:44:30 +0800 Subject: [PATCH 45/46] Change variable name --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 107673f89..745b042e6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -190,8 +190,8 @@ namespace Flow.Launcher.Plugin.Program var _uwpsCount = 0; await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { - var pluginCachePath = Context.CurrentPluginMetadata.PluginCacheDirectoryPath; - FilesFolders.ValidateDirectory(pluginCachePath); + var pluginCacheDirectory = Context.CurrentPluginMetadata.PluginCacheDirectoryPath; + FilesFolders.ValidateDirectory(pluginCacheDirectory); static void MoveFile(string sourcePath, string destinationPath) { @@ -237,19 +237,19 @@ namespace Flow.Launcher.Plugin.Program // Move old cache files to the new cache directory var oldWin32CacheFile = Path.Combine(DataLocation.CacheDirectory, $"{Win32CacheName}.cache"); - var newWin32CacheFile = Path.Combine(pluginCachePath, $"{Win32CacheName}.cache"); + var newWin32CacheFile = Path.Combine(pluginCacheDirectory, $"{Win32CacheName}.cache"); MoveFile(oldWin32CacheFile, newWin32CacheFile); var oldUWPCacheFile = Path.Combine(DataLocation.CacheDirectory, $"{UwpCacheName}.cache"); - var newUWPCacheFile = Path.Combine(pluginCachePath, $"{UwpCacheName}.cache"); + var newUWPCacheFile = Path.Combine(pluginCacheDirectory, $"{UwpCacheName}.cache"); MoveFile(oldUWPCacheFile, newUWPCacheFile); await _win32sLock.WaitAsync(); - _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCachePath, new List()); + _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List()); _win32sCount = _win32s.Count; _win32sLock.Release(); await _uwpsLock.WaitAsync(); - _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCachePath, new List()); + _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List()); _uwpsCount = _uwps.Count; _uwpsLock.Release(); }); From 482e37316a1fee76b924e3b151a4507a42ad2cb7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 8 Apr 2025 19:46:40 +0800 Subject: [PATCH 46/46] Remove useless releases --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 745b042e6..a50868b69 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -258,9 +258,6 @@ namespace Flow.Launcher.Plugin.Program var cacheEmpty = _win32sCount == 0 || _uwpsCount == 0; - _win32sLock.Release(); - _uwpsLock.Release(); - if (cacheEmpty || _settings.LastIndexTime.AddHours(30) < DateTime.Now) { _ = Task.Run(async () =>