From d66bdbb112217f3de88488d1ed7d469e34b23613 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 24 Mar 2025 08:51:24 +0800 Subject: [PATCH 01/37] Force FL window foreground when switching keyboard layout --- Flow.Launcher.Infrastructure/Win32Helper.cs | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 7a3a0c36e..a2d37a2f0 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -124,6 +124,16 @@ namespace Flow.Launcher.Infrastructure return PInvoke.SetForegroundWindow(new(handle)); } + public static bool IsForegroundWindow(Window window) + { + return GetWindowHandle(window).Equals(PInvoke.GetForegroundWindow()); + } + + internal static bool IsForegroundWindow(HWND handle) + { + return handle.Equals(PInvoke.GetForegroundWindow()); + } + #endregion #region Task Switching @@ -354,10 +364,17 @@ namespace Flow.Launcher.Infrastructure // No installed English layout found if (enHKL == HKL.Null) return; - // Get the current foreground window - var hwnd = PInvoke.GetForegroundWindow(); + // Get the FL main window + var hwnd = GetWindowHandle(Application.Current.MainWindow, true); if (hwnd == HWND.Null) return; + // Check if the FL main window is the current foreground window + if (!IsForegroundWindow(hwnd)) + { + var result = PInvoke.SetForegroundWindow(hwnd); + if (!result) throw new Win32Exception(Marshal.GetLastWin32Error()); + } + // Get the current foreground window thread ID var threadId = PInvoke.GetWindowThreadProcessId(hwnd); if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); @@ -367,12 +384,10 @@ namespace Flow.Launcher.Infrastructure // the IME mode instead of switching to another layout. var currentLayout = PInvoke.GetKeyboardLayout(threadId); var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord; - foreach (var langTag in ImeLanguageTags) + foreach (var imeLangTag in ImeLanguageTags) { - if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase)) - { - return; - } + var langTag = GetLanguageTag(currentLangId); + if (langTag.StartsWith(imeLangTag, StringComparison.OrdinalIgnoreCase)) return; } // Backup current keyboard layout From db46a4adfd354c70b0b8fede7f9fe5887f97f7d0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 30 Mar 2025 09:12:14 +0800 Subject: [PATCH 02/37] Code quality --- Flow.Launcher.Infrastructure/Win32Helper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index a2d37a2f0..b5b89e2d1 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -126,7 +126,7 @@ namespace Flow.Launcher.Infrastructure public static bool IsForegroundWindow(Window window) { - return GetWindowHandle(window).Equals(PInvoke.GetForegroundWindow()); + return IsForegroundWindow(GetWindowHandle(window)); } internal static bool IsForegroundWindow(HWND handle) From bc823b3ca44ff4fb730060fe2949e3c27f4de98d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 30 Mar 2025 15:04:07 +0800 Subject: [PATCH 03/37] Fix Application.Current null exception --- Flow.Launcher.Infrastructure/Win32Helper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index b5b89e2d1..f260b17d1 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -364,6 +364,9 @@ namespace Flow.Launcher.Infrastructure // No installed English layout found if (enHKL == HKL.Null) return; + // When application is exiting, the Application.Current will be null + if (Application.Current == null) return; + // Get the FL main window var hwnd = GetWindowHandle(Application.Current.MainWindow, true); if (hwnd == HWND.Null) return; From b62c19e28b0fd8063dc3004dc21a7c840162d3b7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 19:30:15 +0800 Subject: [PATCH 04/37] Move format function position --- Flow.Launcher.Core/Updater.cs | 13 ++++++++++++- Flow.Launcher.Infrastructure/Helper.cs | 22 ---------------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 9a77ece32..96efcacf6 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -17,6 +17,7 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using System.Text.Json.Serialization; using System.Threading; +using System.Text.Json; namespace Flow.Launcher.Core { @@ -51,7 +52,7 @@ namespace Flow.Launcher.Core var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString()); var currentVersion = Version.Parse(Constant.Version); - Log.Info($"|Updater.UpdateApp|Future Release <{newUpdateInfo.FutureReleaseEntry.Formatted()}>"); + Log.Info($"|Updater.UpdateApp|Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>"); if (newReleaseVersion <= currentVersion) { @@ -151,5 +152,15 @@ namespace Flow.Launcher.Core return tips; } + + private static string Formatted(T t) + { + var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions + { + WriteIndented = true + }); + + return formatted; + } } } diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs index 864d796c7..c393c4016 100644 --- a/Flow.Launcher.Infrastructure/Helper.cs +++ b/Flow.Launcher.Infrastructure/Helper.cs @@ -2,18 +2,11 @@ using System; using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; namespace Flow.Launcher.Infrastructure { public static class Helper { - static Helper() - { - jsonFormattedSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - } - /// /// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy /// @@ -71,20 +64,5 @@ namespace Flow.Launcher.Infrastructure Directory.CreateDirectory(path); } } - - private static readonly JsonSerializerOptions jsonFormattedSerializerOptions = new JsonSerializerOptions - { - WriteIndented = true - }; - - public static string Formatted(this T t) - { - var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions - { - WriteIndented = true - }); - - return formatted; - } } } From 44ba60cdfcd58785456c8fcb61a203197005daa5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 2 Apr 2025 20:17:32 +0800 Subject: [PATCH 05/37] Move ValidateDirectory functions to FileFolders for plugin usage --- Flow.Launcher.Infrastructure/Helper.cs | 36 --------------- .../Storage/BinaryStorage.cs | 3 +- .../Storage/FlowLauncherJsonStorage.cs | 5 +- .../Storage/JsonStorage.cs | 3 +- .../Storage/PluginJsonStorage.cs | 3 +- .../SharedCommands/FilesFolders.cs | 46 +++++++++++++++++++ Plugins/Flow.Launcher.Plugin.Program/Main.cs | 4 +- .../Flow.Launcher.Plugin.WebSearch/Main.cs | 3 +- 8 files changed, 58 insertions(+), 45 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs index c393c4016..b02d84ca7 100644 --- a/Flow.Launcher.Infrastructure/Helper.cs +++ b/Flow.Launcher.Infrastructure/Helper.cs @@ -1,7 +1,6 @@ #nullable enable using System; -using System.IO; namespace Flow.Launcher.Infrastructure { @@ -29,40 +28,5 @@ namespace Flow.Launcher.Infrastructure throw new NullReferenceException(); } } - - public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory) - { - if (!Directory.Exists(dataDirectory)) - { - Directory.CreateDirectory(dataDirectory); - } - - foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory)) - { - var data = Path.GetFileName(bundledDataPath); - var dataPath = Path.Combine(dataDirectory, data.NonNull()); - if (!File.Exists(dataPath)) - { - File.Copy(bundledDataPath, dataPath); - } - else - { - var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc; - var time2 = new FileInfo(dataPath).LastWriteTimeUtc; - if (time1 != time2) - { - File.Copy(bundledDataPath, dataPath, true); - } - } - } - } - - public static void ValidateDirectory(string path) - { - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - } } } diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 5b73faae6..a8d5f5d62 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.SharedCommands; using MemoryPack; namespace Flow.Launcher.Infrastructure.Storage @@ -21,7 +22,7 @@ namespace Flow.Launcher.Infrastructure.Storage public BinaryStorage(string filename, string directoryPath = null) { directoryPath ??= DataLocation.CacheDirectory; - Helper.ValidateDirectory(directoryPath); + FilesFolders.ValidateDirectory(directoryPath); FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); } diff --git a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs index 865041fb3..3669bb405 100644 --- a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs @@ -1,5 +1,6 @@ using System.IO; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Infrastructure.Storage { @@ -8,10 +9,10 @@ namespace Flow.Launcher.Infrastructure.Storage public FlowLauncherJsonStorage() { var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName); - Helper.ValidateDirectory(directoryPath); + FilesFolders.ValidateDirectory(directoryPath); var filename = typeof(T).Name; FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 40106acd8..a3488124b 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -5,6 +5,7 @@ using System.IO; using System.Text.Json; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Infrastructure.Storage { @@ -37,7 +38,7 @@ namespace Flow.Launcher.Infrastructure.Storage FilePath = filePath; DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path"); - Helper.ValidateDirectory(DirectoryPath); + FilesFolders.ValidateDirectory(DirectoryPath); } public async Task LoadAsync() diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index b377c81aa..cc78bb8f6 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -1,5 +1,6 @@ using System.IO; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Infrastructure.Storage { @@ -14,7 +15,7 @@ namespace Flow.Launcher.Infrastructure.Storage var dataType = typeof(T); AssemblyName = dataType.Assembly.GetName().Name; DirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, AssemblyName); - Helper.ValidateDirectory(DirectoryPath); + FilesFolders.ValidateDirectory(DirectoryPath); FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); } diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 5f003e351..1de5841a5 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -318,5 +318,51 @@ namespace Flow.Launcher.Plugin.SharedCommands { return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; } + + /// + /// Validates a directory, creating it if it doesn't exist + /// + /// + public static void ValidateDirectory(string path) + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + } + + /// + /// Validates a data directory, synchronizing it by ensuring all files from a bundled source directory exist in it. + /// If files are missing or outdated, they are copied from the bundled directory to the data directory. + /// + /// + /// + public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory) + { + if (!Directory.Exists(dataDirectory)) + { + Directory.CreateDirectory(dataDirectory); + } + + foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory)) + { + var data = Path.GetFileName(bundledDataPath); + if (data == null) continue; + var dataPath = Path.Combine(dataDirectory, data); + if (!File.Exists(dataPath)) + { + File.Copy(bundledDataPath, dataPath); + } + else + { + var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc; + var time2 = new FileInfo(dataPath).LastWriteTimeUtc; + if (time1 != time2) + { + File.Copy(bundledDataPath, dataPath, true); + } + } + } + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 3be23214c..73b4ae9e6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -6,13 +6,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.Program.Programs; using Flow.Launcher.Plugin.Program.Views; using Flow.Launcher.Plugin.Program.Views.Models; +using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Extensions.Caching.Memory; using Path = System.IO.Path; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; @@ -191,7 +191,7 @@ namespace Flow.Launcher.Plugin.Program await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { - Helper.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath); + FilesFolders.ValidateDirectory(Context.CurrentPluginMetadata.PluginCacheDirectoryPath); static void MoveFile(string sourcePath, string destinationPath) { diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index 7ad9715bb..76aeb3250 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.WebSearch @@ -180,7 +179,7 @@ namespace Flow.Launcher.Plugin.WebSearch // Default images directory is in the WebSearch's application folder DefaultImagesDirectory = Path.Combine(pluginDirectory, Images); - Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory); + FilesFolders.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory); // Custom images directory is in the WebSearch's data location folder CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons"); From 5d16216a5519a42c6f201219ddca60ef24289308 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 21:53:09 +0800 Subject: [PATCH 06/37] Fix environment exit --- Flow.Launcher.Core/Configuration/Portable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index 069154364..4f8287c16 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -169,7 +169,7 @@ namespace Flow.Launcher.Core.Configuration { FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s)); - Environment.Exit(0); + Application.Current.Shutdown(); } } // Otherwise, if the portable data folder is marked for deletion, From 96f10d27997f13e24866a415c346ba947e515f98 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 22:13:12 +0800 Subject: [PATCH 07/37] Code quality --- Flow.Launcher.Infrastructure/Win32Helper.cs | 11 +++++++++++ Flow.Launcher/Notification.cs | 5 ++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 7a3a0c36e..42461dc18 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -488,5 +488,16 @@ namespace Flow.Launcher.Infrastructure } #endregion + + #region Noticification + + public static bool IsNotificationSupport() + { + // Noticification only supported Windows 10 19041+ + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.Build >= 19041; + } + + #endregion } } diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs index cb1cbb729..23125de15 100644 --- a/Flow.Launcher/Notification.cs +++ b/Flow.Launcher/Notification.cs @@ -9,8 +9,8 @@ namespace Flow.Launcher { internal static class Notification { - internal static bool legacy = Environment.OSVersion.Version.Build < 19041; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] + internal static bool legacy = !Win32Helper.IsNotificationSupport(); + internal static void Uninstall() { if (!legacy) @@ -25,7 +25,6 @@ namespace Flow.Launcher }); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] private static void ShowInternal(string title, string subTitle, string iconPath = null) { // Handle notification for win7/8/early win10 From b1721f15077c264a79df2642e12a5fd1c1b369fe Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 22:25:04 +0800 Subject: [PATCH 08/37] Code quality --- Flow.Launcher.Core/Updater.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 9a77ece32..5b2144efe 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -4,10 +4,11 @@ using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; using System.Threading.Tasks; using System.Windows; -using JetBrains.Annotations; -using Squirrel; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Resource; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Infrastructure; @@ -15,8 +16,8 @@ using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using System.Text.Json.Serialization; -using System.Threading; +using JetBrains.Annotations; +using Squirrel; namespace Flow.Launcher.Core { @@ -70,7 +71,7 @@ namespace Flow.Launcher.Core if (DataLocation.PortableDataLocationInUse()) { - var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}"; + var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}"; FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)); if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s))) _api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"), @@ -122,7 +123,7 @@ namespace Flow.Launcher.Core } // https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs - private async Task GitHubUpdateManagerAsync(string repository) + private static async Task GitHubUpdateManagerAsync(string repository) { var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; @@ -144,9 +145,9 @@ namespace Flow.Launcher.Core return manager; } - public string NewVersionTips(string version) + private static string NewVersionTips(string version) { - var translator = InternationalizationManager.Instance; + var translator = Ioc.Default.GetRequiredService(); var tips = string.Format(translator.GetTranslation("newVersionTips"), version); return tips; From 0def881b9d74497974124c35327cfa10b6bc35fa Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 22:27:02 +0800 Subject: [PATCH 09/37] Code quality --- Flow.Launcher.Core/Configuration/Portable.cs | 40 ++++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index 4f8287c16..8abdae8d8 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -22,7 +22,7 @@ namespace Flow.Launcher.Core.Configuration /// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish /// /// - private UpdateManager NewUpdateManager() + private static UpdateManager NewUpdateManager() { var applicationFolderName = Constant.ApplicationDirectory .Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None) @@ -81,20 +81,16 @@ namespace Flow.Launcher.Core.Configuration public void RemoveShortcuts() { - using (var portabilityUpdater = NewUpdateManager()) - { - portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu); - portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop); - portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup); - } + using var portabilityUpdater = NewUpdateManager(); + portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu); + portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop); + portabilityUpdater.RemoveShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup); } public void RemoveUninstallerEntry() { - using (var portabilityUpdater = NewUpdateManager()) - { - portabilityUpdater.RemoveUninstallerRegistryEntry(); - } + using var portabilityUpdater = NewUpdateManager(); + portabilityUpdater.RemoveUninstallerRegistryEntry(); } public void MoveUserDataFolder(string fromLocation, string toLocation) @@ -110,12 +106,10 @@ namespace Flow.Launcher.Core.Configuration public void CreateShortcuts() { - using (var portabilityUpdater = NewUpdateManager()) - { - portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false); - portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false); - portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false); - } + using var portabilityUpdater = NewUpdateManager(); + portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.StartMenu, false); + portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Desktop, false); + portabilityUpdater.CreateShortcutsForExecutable(Constant.ApplicationFileName, ShortcutLocation.Startup, false); } public void CreateUninstallerEntry() @@ -129,18 +123,14 @@ namespace Flow.Launcher.Core.Configuration subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String); } - using (var portabilityUpdater = NewUpdateManager()) - { - _ = portabilityUpdater.CreateUninstallerRegistryEntry(); - } + using var portabilityUpdater = NewUpdateManager(); + _ = portabilityUpdater.CreateUninstallerRegistryEntry(); } - internal void IndicateDeletion(string filePathTodelete) + private static void IndicateDeletion(string filePathTodelete) { var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile); - using (var _ = File.CreateText(deleteFilePath)) - { - } + using var _ = File.CreateText(deleteFilePath); } /// From 2f53a79a26fc84cb012d580a37c9c2bab7eb7965 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 22:56:31 +0800 Subject: [PATCH 10/37] Revert "Fix environment exit" This reverts commit 5d16216a5519a42c6f201219ddca60ef24289308. --- Flow.Launcher.Core/Configuration/Portable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index 8abdae8d8..2b570d2c0 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -159,7 +159,7 @@ namespace Flow.Launcher.Core.Configuration { FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s)); - Application.Current.Shutdown(); + Environment.Exit(0); } } // Otherwise, if the portable data folder is marked for deletion, From b725975b9898e249a7382171cf2edb07437871d0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 3 Apr 2025 23:01:16 +0800 Subject: [PATCH 11/37] Fix environment exit stuck issue --- Flow.Launcher/App.xaml.cs | 8 ++++++++ Flow.Launcher/MainWindow.xaml.cs | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 9aee56bff..f484d4dba 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -304,6 +304,14 @@ namespace Flow.Launcher return; } + // If we call Environment.Exit(0), the application dispose will be called before _mainWindow.Close() + // Accessing _mainWindow?.Dispatcher will cause the application stuck + // So here we need to check it and just return so that we will not acees _mainWindow?.Dispatcher + if (!_mainWindow.CanClose) + { + return; + } + _disposed = true; } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 011d46d6b..c62606743 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -32,6 +32,13 @@ namespace Flow.Launcher { public partial class MainWindow : IDisposable { + #region Public Property + + // Window Event: Close Event + public bool CanClose { get; set; } = false; + + #endregion + #region Private Fields // Dependency Injection @@ -45,8 +52,6 @@ namespace Flow.Launcher private readonly ContextMenu _contextMenu = new(); private readonly MainViewModel _viewModel; - // Window Event: Close Event - private bool _canClose = false; // Window Event: Key Event private bool _isArrowKeyPressed = false; @@ -279,7 +284,7 @@ namespace Flow.Launcher private async void OnClosing(object sender, CancelEventArgs e) { - if (!_canClose) + if (!CanClose) { _notifyIcon.Visible = false; App.API.SaveAppAllSettings(); @@ -287,7 +292,7 @@ namespace Flow.Launcher await PluginManager.DisposePluginsAsync(); Notification.Uninstall(); // After plugins are all disposed, we can close the main window - _canClose = true; + CanClose = true; // Use this instead of Close() to avoid InvalidOperationException when calling Close() in OnClosing event Application.Current.Shutdown(); } From 95b38d21af7b8790659d9eb49e2edbbba9a037b1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 09:57:03 +0800 Subject: [PATCH 12/37] Wait image cache saved before restarting --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 8 +++++++- Flow.Launcher/PublicAPIInstance.cs | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 6f7b1cd90..0ba059b7b 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -61,7 +61,7 @@ namespace Flow.Launcher.Infrastructure.Image }); } - public static async Task Save() + public static async Task SaveAsync() { await storageLock.WaitAsync(); @@ -77,6 +77,12 @@ namespace Flow.Launcher.Infrastructure.Image } } + public static async Task WaitSaveAsync() + { + await storageLock.WaitAsync(); + storageLock.Release(); + } + private static async Task> LoadStorageToConcurrentDictionaryAsync() { await storageLock.WaitAsync(); diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e19ad2fdc..b86e731b3 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -57,7 +57,7 @@ namespace Flow.Launcher _mainVM.ChangeQueryText(query, requery); } - public void RestartApp() + public async void RestartApp() { _mainVM.Hide(); @@ -66,6 +66,9 @@ namespace Flow.Launcher // which will cause ungraceful exit SaveAppAllSettings(); + // wait for all image caches to be saved + await ImageLoader.WaitSaveAsync(); + // Restart requires Squirrel's Update.exe to be present in the parent folder, // it is only published from the project's release pipeline. When debugging without it, // the project may not restart or just terminates. This is expected. @@ -88,7 +91,7 @@ namespace Flow.Launcher PluginManager.Save(); _mainVM.Save(); _settings.Save(); - _ = ImageLoader.Save(); + _ = ImageLoader.SaveAsync(); } public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync(); From e4577eb23edbe6ce4e13154d2588d9e42e494764 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 10:02:47 +0800 Subject: [PATCH 13/37] Improve code comment --- Flow.Launcher/PublicAPIInstance.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index b86e731b3..a9862c74c 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -57,16 +57,18 @@ namespace Flow.Launcher _mainVM.ChangeQueryText(query, requery); } +#pragma warning disable VSTHRD100 // Avoid async void methods + public async void RestartApp() { _mainVM.Hide(); - // we must manually save + // We must manually save // UpdateManager.RestartApp() will call Environment.Exit(0) // which will cause ungraceful exit SaveAppAllSettings(); - // wait for all image caches to be saved + // Wait for all image caches to be saved before restarting await ImageLoader.WaitSaveAsync(); // Restart requires Squirrel's Update.exe to be present in the parent folder, @@ -75,6 +77,8 @@ namespace Flow.Launcher UpdateManager.RestartApp(Constant.ApplicationFileName); } +#pragma warning restore VSTHRD100 // Avoid async void methods + public void ShowMainWindow() => _mainVM.Show(); public void HideMainWindow() => _mainVM.Hide(); From 043fe76a613a5768bcd0e983acb555c58a8d681f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 10:03:19 +0800 Subject: [PATCH 14/37] Add settings save lock --- Flow.Launcher/PublicAPIInstance.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index a9862c74c..d88eeb7c9 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -37,6 +37,8 @@ namespace Flow.Launcher private readonly Internationalization _translater; private readonly MainViewModel _mainVM; + private readonly object _saveSettingsLock = new(); + #region Constructor public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM) @@ -92,9 +94,12 @@ namespace Flow.Launcher public void SaveAppAllSettings() { - PluginManager.Save(); - _mainVM.Save(); - _settings.Save(); + lock (_saveSettingsLock) + { + _settings.Save(); + PluginManager.Save(); + _mainVM.Save(); + } _ = ImageLoader.SaveAsync(); } From 95a3fd36dea778911d0f4f8b9a434e4813096b6a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 10:34:59 +0800 Subject: [PATCH 15/37] Do not crash when caught exception on saving settings --- .../Plugin/JsonRPCPluginSettings.cs | 20 +++++++++-- .../Storage/FlowLauncherJsonStorage.cs | 35 ++++++++++++++++++- .../Storage/PluginJsonStorage.cs | 33 +++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs index 944b2fd10..e0a217251 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -23,6 +23,8 @@ namespace Flow.Launcher.Core.Plugin protected ConcurrentDictionary Settings { get; set; } = null!; public required IPublicAPI API { get; init; } + private static readonly string ClassName = nameof(JsonRPCPluginSettings); + private JsonStorage> _storage = null!; private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin"); @@ -122,12 +124,26 @@ namespace Flow.Launcher.Core.Plugin public async Task SaveAsync() { - await _storage.SaveAsync(); + try + { + await _storage.SaveAsync(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e); + } } public void Save() { - _storage.Save(); + try + { + _storage.Save(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save plugin settings to path: {SettingPath}", e); + } } public bool NeedCreateSettingPanel() diff --git a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs index 865041fb3..a3634a0e2 100644 --- a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs @@ -1,10 +1,19 @@ using System.IO; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.Storage { public class FlowLauncherJsonStorage : JsonStorage where T : new() { + private static readonly string ClassName = "FlowLauncherJsonStorage"; + + // 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 FlowLauncherJsonStorage() { var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName); @@ -13,5 +22,29 @@ namespace Flow.Launcher.Infrastructure.Storage var filename = typeof(T).Name; FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); } + + public new void Save() + { + try + { + base.Save(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e); + } + } + + public new async Task SaveAsync() + { + try + { + await base.SaveAsync(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e); + } + } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index b377c81aa..e02d51bdb 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -1,5 +1,8 @@ using System.IO; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.Storage { @@ -8,6 +11,12 @@ namespace Flow.Launcher.Infrastructure.Storage // Use assembly name to check which plugin is using this storage public readonly string AssemblyName; + private static readonly string ClassName = "PluginJsonStorage"; + + // 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 PluginJsonStorage() { // C# related, add python related below @@ -23,5 +32,29 @@ namespace Flow.Launcher.Infrastructure.Storage { Data = data; } + + public new void Save() + { + try + { + base.Save(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e); + } + } + + public new async Task SaveAsync() + { + try + { + await base.SaveAsync(); + } + catch (System.Exception e) + { + API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e); + } + } } } From f6e3608f72ab28c48bf8563ea3bb58af7b9950bb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 10:42:48 +0800 Subject: [PATCH 16/37] Do not crash when saving cache --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 0ba059b7b..1ee033821 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -71,6 +71,10 @@ namespace Flow.Launcher.Infrastructure.Image .Select(x => x.Key) .ToList()); } + catch (System.Exception e) + { + Log.Exception($"|ImageLoader.SaveAsync|Failed to save image cache to file", e); + } finally { storageLock.Release(); From 2d6667bb53549c5580004fe194b56b90d20cdcd7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 10:42:55 +0800 Subject: [PATCH 17/37] Test save error --- Flow.Launcher.Infrastructure/Storage/JsonStorage.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 40106acd8..52bcdcfab 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -180,20 +180,24 @@ namespace Flow.Launcher.Infrastructure.Storage public void Save() { - string serialized = JsonSerializer.Serialize(Data, + throw new NotImplementedException("Save error"); + + /*string serialized = JsonSerializer.Serialize(Data, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(TempFilePath, serialized); - AtomicWriteSetting(); + AtomicWriteSetting();*/ } public async Task SaveAsync() { - await using var tempOutput = File.OpenWrite(TempFilePath); + throw new NotImplementedException("SaveAsync error"); + + /*await using var tempOutput = File.OpenWrite(TempFilePath); await JsonSerializer.SerializeAsync(tempOutput, Data, new JsonSerializerOptions { WriteIndented = true }); - AtomicWriteSetting(); + AtomicWriteSetting();*/ } private void AtomicWriteSetting() From c2f8480c049a4a818831c05558080907f2bac8d0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:15:13 +0800 Subject: [PATCH 18/37] Revert "Test save error" This reverts commit 2d6667bb53549c5580004fe194b56b90d20cdcd7. --- Flow.Launcher.Infrastructure/Storage/JsonStorage.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 52bcdcfab..40106acd8 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -180,24 +180,20 @@ namespace Flow.Launcher.Infrastructure.Storage public void Save() { - throw new NotImplementedException("Save error"); - - /*string serialized = JsonSerializer.Serialize(Data, + string serialized = JsonSerializer.Serialize(Data, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(TempFilePath, serialized); - AtomicWriteSetting();*/ + AtomicWriteSetting(); } public async Task SaveAsync() { - throw new NotImplementedException("SaveAsync error"); - - /*await using var tempOutput = File.OpenWrite(TempFilePath); + await using var tempOutput = File.OpenWrite(TempFilePath); await JsonSerializer.SerializeAsync(tempOutput, Data, new JsonSerializerOptions { WriteIndented = true }); - AtomicWriteSetting();*/ + AtomicWriteSetting(); } private void AtomicWriteSetting() From 2a2ef234d952be3bc44507f95248b1622b92256d Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:17:30 +0800 Subject: [PATCH 19/37] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Infrastructure/Win32Helper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 42461dc18..f2b99588d 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -489,8 +489,7 @@ namespace Flow.Launcher.Infrastructure #endregion - #region Noticification - + #region Notification public static bool IsNotificationSupport() { // Noticification only supported Windows 10 19041+ From 834780d6e7d57577a59f35c0ad5715f99e7b61cf Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:17:37 +0800 Subject: [PATCH 20/37] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher.Infrastructure/Win32Helper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index f2b99588d..5bec3e5e7 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -492,7 +492,7 @@ namespace Flow.Launcher.Infrastructure #region Notification public static bool IsNotificationSupport() { - // Noticification only supported Windows 10 19041+ + // Notification only supported Windows 10 19041+ return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Build >= 19041; } From 8df1e6a0ce9977346327c9e9249ef2359c3a9ad1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:18:57 +0800 Subject: [PATCH 21/37] Add blank line --- Flow.Launcher.Infrastructure/Win32Helper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 5bec3e5e7..d3023a3f0 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -490,6 +490,7 @@ namespace Flow.Launcher.Infrastructure #endregion #region Notification + public static bool IsNotificationSupport() { // Notification only supported Windows 10 19041+ From 3283adce59384003887d5d91dfa767ca6822c92e Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:27:10 +0800 Subject: [PATCH 22/37] Fix typos Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Infrastructure/Win32Helper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index d3023a3f0..c21849403 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -491,9 +491,9 @@ namespace Flow.Launcher.Infrastructure #region Notification - public static bool IsNotificationSupport() + public static bool IsNotificationSupported() { - // Notification only supported Windows 10 19041+ + // Notifications only supported on Windows 10 19041+ return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Build >= 19041; } From ec7a4e8aaa1463524ae0cacebceb5ec7f89e94b8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:27:50 +0800 Subject: [PATCH 23/37] Fix build issue --- Flow.Launcher/Notification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs index 23125de15..30b3a0673 100644 --- a/Flow.Launcher/Notification.cs +++ b/Flow.Launcher/Notification.cs @@ -9,7 +9,7 @@ namespace Flow.Launcher { internal static class Notification { - internal static bool legacy = !Win32Helper.IsNotificationSupport(); + internal static bool legacy = !Win32Helper.IsNotificationSupported(); internal static void Uninstall() { From 28ab71f11a60304db888c465e871c24be5b18ab5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 11:46:07 +0800 Subject: [PATCH 24/37] Fix build issue --- Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs | 1 + Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs index 0fbeed9f5..8b4062b6b 100644 --- a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Infrastructure.Storage { diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index 910672119..e8cbd70fb 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Infrastructure.Storage { From df84e02f55d87404d1f9fe07299d20747072900c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 15:37:03 +0800 Subject: [PATCH 25/37] Improve code quality --- ...Flow.Launcher.Plugin.PluginsManager.csproj | 3 +- .../Main.cs | 13 ++++----- .../PluginsManager.cs | 29 +++++++++---------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index c33c42889..5a2259ff1 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -18,8 +18,7 @@ - - + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index 156135f81..b333aba42 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -1,18 +1,17 @@ -using Flow.Launcher.Core.ExternalPlugins; -using Flow.Launcher.Plugin.PluginsManager.ViewModels; -using Flow.Launcher.Plugin.PluginsManager.Views; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Windows.Controls; -using Flow.Launcher.Infrastructure; using System.Threading.Tasks; using System.Threading; +using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Plugin.PluginsManager.ViewModels; +using Flow.Launcher.Plugin.PluginsManager.Views; namespace Flow.Launcher.Plugin.PluginsManager { public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n { - internal PluginInitContext Context { get; set; } + internal static PluginInitContext Context { get; set; } internal Settings Settings; @@ -56,7 +55,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Settings.UpdateCommand => await pluginManager.RequestUpdateAsync(query.SecondToEndSearch, token, query.IsReQuery), _ => pluginManager.GetDefaultHotKeys().Where(hotkey => { - hotkey.Score = StringMatcher.FuzzySearch(query.Search, hotkey.Title).Score; + hotkey.Score = Context.API.FuzzySearch(query.Search, hotkey.Title).Score; return hotkey.Score > 0; }).ToList() }; diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 79d6aedd5..9cee8bc8f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -1,8 +1,5 @@ using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Http; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; @@ -17,7 +14,9 @@ namespace Flow.Launcher.Plugin.PluginsManager { internal class PluginsManager { - private const string zip = "zip"; + private const string ZipSuffix = "zip"; + + private static readonly string ClassName = nameof(PluginsManager); private PluginInitContext Context { get; set; } @@ -169,7 +168,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsgError( string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name), Context.API.GetTranslation("plugin_pluginsmanager_download_error")); - Log.Exception("PluginsManager", "An error occurred while downloading plugin", e); + Context.API.LogException(ClassName, "An error occurred while downloading plugin", e); return; } @@ -179,7 +178,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name)); - Log.Exception("PluginsManager", "An error occurred while downloading plugin", e); + Context.API.LogException(ClassName, "An error occurred while downloading plugin", e); return; } @@ -366,7 +365,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } }).ContinueWith(t => { - Log.Exception("PluginsManager", $"Update failed for {x.Name}", + Context.API.LogException(ClassName, $"Update failed for {x.Name}", t.Exception.InnerException); Context.API.ShowMsg( Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), @@ -438,7 +437,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } catch (Exception ex) { - Log.Exception("PluginsManager", $"Update failed for {plugin.Name}", ex.InnerException); + Context.API.LogException(ClassName, $"Update failed for {plugin.Name}", ex.InnerException); Context.API.ShowMsg( Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format( @@ -486,7 +485,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return results .Where(x => { - var matchResult = StringMatcher.FuzzySearch(searchName, x.Title); + var matchResult = Context.API.FuzzySearch(searchName, x.Title); if (matchResult.IsSearchPrecisionScoreMet()) x.Score = matchResult.Score; @@ -498,7 +497,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal List InstallFromWeb(string url) { var filename = url.Split("/").Last(); - var name = filename.Split(string.Format(".{0}", zip)).First(); + var name = filename.Split(string.Format(".{0}", ZipSuffix)).First(); var plugin = new UserPlugin { @@ -605,7 +604,7 @@ namespace Flow.Launcher.Plugin.PluginsManager await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); if (Uri.IsWellFormedUriString(search, UriKind.Absolute) - && search.Split('.').Last() == zip) + && search.Split('.').Last() == ZipSuffix) return InstallFromWeb(search); if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) @@ -656,21 +655,21 @@ namespace Flow.Launcher.Plugin.PluginsManager { Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile")); - Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + Context.API.LogException(ClassName, e.Message, e); } catch (InvalidOperationException e) { Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name)); - Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + Context.API.LogException(ClassName, e.Message, e); } catch (ArgumentException e) { Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name)); - Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + Context.API.LogException(ClassName, e.Message, e); } } @@ -744,7 +743,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } catch (ArgumentException e) { - Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + Context.API.LogException(ClassName, e.Message, e); Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"), Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error")); } From e2d9148702aa47a64d02c915fda77f80b165bc54 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 15:52:57 +0800 Subject: [PATCH 26/37] Move user plugin to plugin project --- .../ExternalPlugins/CommunityPluginSource.cs | 1 + .../ExternalPlugins/CommunityPluginStore.cs | 1 + .../ExternalPlugins/PluginsManifest.cs | 1 + .../ExternalPlugins/UserPlugin.cs | 23 ------ Flow.Launcher.Plugin/UserPlugin.cs | 80 +++++++++++++++++++ .../ViewModel/PluginStoreItemViewModel.cs | 2 - .../ContextMenu.cs | 3 +- .../Utilities.cs | 3 +- 8 files changed, 85 insertions(+), 29 deletions(-) delete mode 100644 Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs create mode 100644 Flow.Launcher.Plugin/UserPlugin.cs diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs index 68be746f2..e9713564e 100644 --- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs +++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs @@ -1,5 +1,6 @@ using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Plugin; using System; using System.Collections.Generic; using System.Net; diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs index affd7c312..1f23c2f66 100644 --- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs +++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.ExternalPlugins { diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs index ac8abcdcc..4f5c4ae40 100644 --- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs +++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Plugin; using System; using System.Collections.Generic; using System.Threading; diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs deleted file mode 100644 index 79d6d7605..000000000 --- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Flow.Launcher.Core.ExternalPlugins -{ - public record UserPlugin - { - public string ID { get; set; } - public string Name { get; set; } - public string Description { get; set; } - public string Author { get; set; } - public string Version { get; set; } - public string Language { get; set; } - public string Website { get; set; } - public string UrlDownload { get; set; } - public string UrlSourceCode { get; set; } - public string LocalInstallPath { get; set; } - public string IcoPath { get; set; } - public DateTime? LatestReleaseDate { get; set; } - public DateTime? DateAdded { get; set; } - - public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); - } -} diff --git a/Flow.Launcher.Plugin/UserPlugin.cs b/Flow.Launcher.Plugin/UserPlugin.cs new file mode 100644 index 000000000..5c9189ae1 --- /dev/null +++ b/Flow.Launcher.Plugin/UserPlugin.cs @@ -0,0 +1,80 @@ +using System; + +namespace Flow.Launcher.Plugin +{ + /// + /// User Plugin Model for Flow Launcher + /// + public record UserPlugin + { + /// + /// Unique identifier of the plugin + /// + public string ID { get; set; } + + /// + /// Name of the plugin + /// + public string Name { get; set; } + + /// + /// Description of the plugin + /// + public string Description { get; set; } + + /// + /// Author of the plugin + /// + public string Author { get; set; } + + /// + /// Version of the plugin + /// + public string Version { get; set; } + + /// + /// Allow language of the plugin + /// + public string Language { get; set; } + + /// + /// Website of the plugin + /// + public string Website { get; set; } + + /// + /// URL to download the plugin + /// + public string UrlDownload { get; set; } + + /// + /// URL to the source code of the plugin + /// + public string UrlSourceCode { get; set; } + + /// + /// URL to the issue tracker of the plugin + /// + public string LocalInstallPath { get; set; } + + /// + /// Icon path of the plugin + /// + public string IcoPath { get; set; } + + /// + /// The date when the plugin was last updated + /// + public DateTime? LatestReleaseDate { get; set; } + + /// + /// The date when the plugin was added to the local system + /// + public DateTime? DateAdded { get; set; } + + /// + /// The date when the plugin was last updated on the local system + /// + public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); + } +} diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs index 38b5bec65..d1cf74501 100644 --- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs @@ -1,10 +1,8 @@ using System; using System.Linq; using CommunityToolkit.Mvvm.Input; -using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; -using SemanticVersioning; using Version = SemanticVersioning.Version; namespace Flow.Launcher.ViewModel diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index 482e821dc..265657ef4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -1,5 +1,4 @@ -using Flow.Launcher.Core.ExternalPlugins; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace Flow.Launcher.Plugin.PluginsManager diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs index 743f5b25b..4bb78f6ff 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs @@ -1,5 +1,4 @@ -using Flow.Launcher.Core.ExternalPlugins; -using ICSharpCode.SharpZipLib.Zip; +using ICSharpCode.SharpZipLib.Zip; using System.IO; using System.IO.Compression; using System.Linq; From b9c0eb7b7859d5288f1cb53bb60be110ed669210 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 15:56:18 +0800 Subject: [PATCH 27/37] Code quality --- .../ExternalPlugins/PluginsManifest.cs | 12 ++++++------ .../PluginsManager.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs index 4f5c4ae40..44d3ef0ff 100644 --- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs +++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs @@ -1,9 +1,9 @@ -using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Plugin; -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.ExternalPlugins { @@ -18,11 +18,11 @@ namespace Flow.Launcher.Core.ExternalPlugins private static readonly SemaphoreSlim manifestUpdateLock = new(1); private static DateTime lastFetchedAt = DateTime.MinValue; - private static TimeSpan fetchTimeout = TimeSpan.FromMinutes(2); + private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2); public static List UserPlugins { get; private set; } - public static async Task UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false) + public static async Task UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) { try { @@ -44,7 +44,7 @@ namespace Flow.Launcher.Core.ExternalPlugins } catch (Exception e) { - Log.Exception($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http request failed", e); + Ioc.Default.GetRequiredService().LogException(nameof(PluginsManifest), "Http request failed", e); } finally { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 9cee8bc8f..9d2a8fe73 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -236,7 +236,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false) { - await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); + await PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token); var pluginFromLocalPath = null as UserPlugin; var updateFromLocalPath = false; @@ -601,7 +601,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal async ValueTask> RequestInstallOrUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false) { - await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); + await PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token); if (Uri.IsWellFormedUriString(search, UriKind.Absolute) && search.Split('.').Last() == ZipSuffix) From 55d1754ed6941835745baf7a64efac7be05a5cb1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 16:05:33 +0800 Subject: [PATCH 28/37] Move PluginManifest public function to api --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 18 ++++++++++++++++++ Flow.Launcher/PublicAPIInstance.cs | 6 ++++++ .../SettingsPanePluginStoreViewModel.cs | 5 ++--- .../Main.cs | 3 +-- .../PluginsManager.cs | 12 +++++------- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f178ebb90..513c2da84 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -344,5 +344,23 @@ namespace Flow.Launcher.Plugin /// Stop the loading bar in main window /// public void StopLoadingBar(); + + /// + /// Update the plugin manifest + /// + /// + /// FL has multiple urls to download the plugin manifest. Set this to true to only use the primary url. + /// + /// + /// + /// True if the manifest is updated successfully, false otherwise. + /// + public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default); + + /// + /// Get the plugin manifest + /// + /// + public IReadOnlyList GetUserPlugins(); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index d88eeb7c9..a8ec46982 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -28,6 +28,7 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.ViewModel; using JetBrains.Annotations; using Flow.Launcher.Core.Resource; +using Flow.Launcher.Core.ExternalPlugins; namespace Flow.Launcher { @@ -354,6 +355,11 @@ namespace Flow.Launcher public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress); + public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) => + PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token); + + public IReadOnlyList GetUserPlugins() => PluginsManifest.UserPlugins; + #endregion #region Private Methods diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index 15579a61d..23a316304 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; -using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; @@ -14,7 +13,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel public string FilterText { get; set; } = string.Empty; public IList ExternalPlugins => - PluginsManifest.UserPlugins?.Select(p => new PluginStoreItemViewModel(p)) + App.API.GetUserPlugins()?.Select(p => new PluginStoreItemViewModel(p)) .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease) .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated) .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None) @@ -24,7 +23,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel [RelayCommand] private async Task RefreshExternalPluginsAsync() { - if (await PluginsManifest.UpdateManifestAsync()) + if (await App.API.UpdatePluginManifestAsync()) { OnPropertyChanged(nameof(ExternalPlugins)); } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs index b333aba42..742d85fc1 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Main.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Windows.Controls; using System.Threading.Tasks; using System.Threading; -using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Plugin.PluginsManager.ViewModels; using Flow.Launcher.Plugin.PluginsManager.Views; @@ -34,7 +33,7 @@ namespace Flow.Launcher.Plugin.PluginsManager contextMenu = new ContextMenu(Context); pluginManager = new PluginsManager(Context, Settings); - await PluginsManifest.UpdateManifestAsync(); + await Context.API.UpdatePluginManifestAsync(); } public List LoadContextMenus(Result selectedResult) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 9d2a8fe73..c742e457c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -1,5 +1,4 @@ -using Flow.Launcher.Core.ExternalPlugins; -using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; @@ -236,7 +235,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false) { - await PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token); + await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); var pluginFromLocalPath = null as UserPlugin; var updateFromLocalPath = false; @@ -249,7 +248,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } var updateSource = !updateFromLocalPath - ? PluginsManifest.UserPlugins + ? Context.API.GetUserPlugins() : new List { pluginFromLocalPath }; var resultsForUpdate = ( @@ -601,7 +600,7 @@ namespace Flow.Launcher.Plugin.PluginsManager internal async ValueTask> RequestInstallOrUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false) { - await PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token); + await Context.API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token); if (Uri.IsWellFormedUriString(search, UriKind.Absolute) && search.Split('.').Last() == ZipSuffix) @@ -611,8 +610,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return InstallFromLocalPath(search); var results = - PluginsManifest - .UserPlugins + Context.API.GetUserPlugins() .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID)) .Select(x => new Result From 4744ff780e91ef36c7f835a7cb7a9b3c02a52af5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 16:33:03 +0800 Subject: [PATCH 29/37] Move PluginManager public function to api --- Flow.Launcher.Core/Plugin/PluginManager.cs | 43 +++++++---------- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 47 +++++++++++++++++-- Flow.Launcher/PublicAPIInstance.cs | 13 ++++- .../SettingsPanePluginStoreViewModel.cs | 2 +- .../PluginsManager.cs | 31 ++++++------ 5 files changed, 87 insertions(+), 49 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 17517832b..aa6c54a94 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -454,16 +454,11 @@ namespace Flow.Launcher.Core.Plugin #region Public functions - public static bool PluginModified(string uuid) + public static bool PluginModified(string id) { - return _modifiedPlugins.Contains(uuid); + return _modifiedPlugins.Contains(id); } - - /// - /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, - /// unless it's a local path installation - /// public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { InstallPlugin(newVersion, zipFilePath, checkModified:false); @@ -471,17 +466,11 @@ namespace Flow.Launcher.Core.Plugin _modifiedPlugins.Add(existingVersion.ID); } - /// - /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation - /// public static void InstallPlugin(UserPlugin plugin, string zipFilePath) { InstallPlugin(plugin, zipFilePath, checkModified: true); } - /// - /// Uninstall a plugin. - /// public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false) { await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true); @@ -525,20 +514,20 @@ namespace Flow.Launcher.Core.Plugin var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; var defaultPluginIDs = new List - { - "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark - "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator - "572be03c74c642baae319fc283e561a8", // Explorer - "6A122269676E40EB86EB543B945932B9", // PluginIndicator - "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager - "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller - "791FC278BA414111B8D1886DFE447410", // Program - "D409510CD0D2481F853690A07E6DC426", // Shell - "CEA08895D2544B019B2E9C5009600DF4", // Sys - "0308FD86DE0A4DEE8D62B9B535370992", // URL - "565B73353DBF4806919830B9202EE3BF", // WebSearch - "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings - }; + { + "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark + "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator + "572be03c74c642baae319fc283e561a8", // Explorer + "6A122269676E40EB86EB543B945932B9", // PluginIndicator + "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager + "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller + "791FC278BA414111B8D1886DFE447410", // Program + "D409510CD0D2481F853690A07E6DC426", // Shell + "CEA08895D2544B019B2E9C5009600DF4", // Sys + "0308FD86DE0A4DEE8D62B9B535370992", // URL + "565B73353DBF4806919830B9202EE3BF", // WebSearch + "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings + }; // Treat default plugin differently, it needs to be removable along with each flow release var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 513c2da84..eeb3f5de3 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -352,15 +352,54 @@ namespace Flow.Launcher.Plugin /// FL has multiple urls to download the plugin manifest. Set this to true to only use the primary url. /// /// - /// - /// True if the manifest is updated successfully, false otherwise. - /// + /// True if the manifest is updated successfully, false otherwise public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default); /// /// Get the plugin manifest /// /// - public IReadOnlyList GetUserPlugins(); + public IReadOnlyList GetPluginManifest(); + + /// + /// Check if the plugin has been modified. + /// If this plugin is updated, installed or uninstalled and users do not restart the app, + /// it will be marked as modified + /// + /// Plugin id + /// + public bool PluginModified(string id); + + /// + /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, + /// unless it's a local path installation + /// + /// The metadata of the old plugin to update + /// The new plugin to update + /// + /// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed. + /// + /// + public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath); + + /// + /// Install a plugin. By default will remove the zip file if installation is from url, + /// unless it's a local path installation + /// + /// The plugin to install + /// + /// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed. + /// + public void InstallPlugin(UserPlugin plugin, string zipFilePath); + + /// + /// Uninstall a plugin + /// + /// The metadata of the plugin to uninstall + /// + /// Plugin has their own settings. If this is set to true, the plugin settings will be removed. + /// + /// + public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false); } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index a8ec46982..c40e40ebb 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -358,7 +358,18 @@ namespace Flow.Launcher public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) => PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token); - public IReadOnlyList GetUserPlugins() => PluginsManifest.UserPlugins; + public IReadOnlyList GetPluginManifest() => PluginsManifest.UserPlugins; + + public bool PluginModified(string id) => PluginManager.PluginModified(id); + + public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) => + PluginManager.UpdatePluginAsync(pluginMetadata, plugin, zipFilePath); + + public void InstallPlugin(UserPlugin plugin, string zipFilePath) => + PluginManager.InstallPlugin(plugin, zipFilePath); + + public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) => + PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings); #endregion diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index 23a316304..84d8a2ff9 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -13,7 +13,7 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel public string FilterText { get; set; } = string.Empty; public IList ExternalPlugins => - App.API.GetUserPlugins()?.Select(p => new PluginStoreItemViewModel(p)) + App.API.GetPluginManifest()?.Select(p => new PluginStoreItemViewModel(p)) .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease) .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated) .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index c742e457c..a16778ff4 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -1,6 +1,4 @@ -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Plugin.SharedCommands; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,6 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Windows; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.PluginsManager { @@ -49,7 +48,7 @@ namespace Flow.Launcher.Plugin.PluginsManager { return new List() { - new Result() + new() { Title = Settings.InstallCommand, IcoPath = icoPath, @@ -61,7 +60,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return false; } }, - new Result() + new() { Title = Settings.UninstallCommand, IcoPath = icoPath, @@ -73,7 +72,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return false; } }, - new Result() + new() { Title = Settings.UpdateCommand, IcoPath = icoPath, @@ -248,7 +247,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } var updateSource = !updateFromLocalPath - ? Context.API.GetUserPlugins() + ? Context.API.GetPluginManifest() : new List { pluginFromLocalPath }; var resultsForUpdate = ( @@ -258,7 +257,7 @@ namespace Flow.Launcher.Plugin.PluginsManager where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, StringComparison.InvariantCulture) < 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) - && !PluginManager.PluginModified(existingPlugin.Metadata.ID) + && !Context.API.PluginModified(existingPlugin.Metadata.ID) select new { @@ -274,7 +273,7 @@ namespace Flow.Launcher.Plugin.PluginsManager if (!resultsForUpdate.Any()) return new List { - new Result + new() { Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), @@ -339,7 +338,7 @@ namespace Flow.Launcher.Plugin.PluginsManager } else { - await PluginManager.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin, + await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); if (Settings.AutoRestartAfterChanging) @@ -431,7 +430,7 @@ namespace Flow.Launcher.Plugin.PluginsManager if (cts.IsCancellationRequested) return; else - await PluginManager.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, + await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath); } catch (Exception ex) @@ -550,7 +549,7 @@ namespace Flow.Launcher.Plugin.PluginsManager return new List { - new Result + new() { Title = $"{plugin.Name} by {plugin.Author}", SubTitle = plugin.Description, @@ -610,8 +609,8 @@ namespace Flow.Launcher.Plugin.PluginsManager return InstallFromLocalPath(search); var results = - Context.API.GetUserPlugins() - .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID)) + Context.API.GetPluginManifest() + .Where(x => !PluginExists(x.ID) && !Context.API.PluginModified(x.ID)) .Select(x => new Result { @@ -644,7 +643,7 @@ namespace Flow.Launcher.Plugin.PluginsManager try { - PluginManager.InstallPlugin(plugin, downloadedFilePath); + Context.API.InstallPlugin(plugin, downloadedFilePath); if (!plugin.IsFromLocalInstallPath) File.Delete(downloadedFilePath); @@ -737,7 +736,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"), Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"), button: MessageBoxButton.YesNo) == MessageBoxResult.No; - await PluginManager.UninstallPluginAsync(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings); + await Context.API.UninstallPluginAsync(plugin, removePluginSettings); } catch (ArgumentException e) { From 473b139ea4a6b43346aa79c582ff66f0094477ca Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 4 Apr 2025 16:35:28 +0800 Subject: [PATCH 30/37] Move FL project reference --- .../Flow.Launcher.Plugin.PluginsManager.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 5a2259ff1..8ff41a7ad 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -18,7 +18,6 @@ - @@ -36,4 +35,8 @@ PreserveNewest + + + + From cbd8d2272386ce4e28b688c9cd3b7bc8a16e832e Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 17:24:21 +0800 Subject: [PATCH 31/37] Improve documents Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/UserPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/UserPlugin.cs b/Flow.Launcher.Plugin/UserPlugin.cs index 5c9189ae1..3488b4b93 100644 --- a/Flow.Launcher.Plugin/UserPlugin.cs +++ b/Flow.Launcher.Plugin/UserPlugin.cs @@ -73,7 +73,7 @@ namespace Flow.Launcher.Plugin public DateTime? DateAdded { get; set; } /// - /// The date when the plugin was last updated on the local system + /// Indicates whether the plugin is installed from a local path /// public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); } From 171ebe955f23491ad6a70b3a6c197313f7aa47b0 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Fri, 4 Apr 2025 17:24:36 +0800 Subject: [PATCH 32/37] Improve documents Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Plugin/UserPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/UserPlugin.cs b/Flow.Launcher.Plugin/UserPlugin.cs index 3488b4b93..74a16b83d 100644 --- a/Flow.Launcher.Plugin/UserPlugin.cs +++ b/Flow.Launcher.Plugin/UserPlugin.cs @@ -53,7 +53,7 @@ namespace Flow.Launcher.Plugin public string UrlSourceCode { get; set; } /// - /// URL to the issue tracker of the plugin + /// Local path where the plugin is installed /// public string LocalInstallPath { get; set; } From afba3c01cc145646cf887a0c8b4f3406725f8d6d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 12:52:26 +0800 Subject: [PATCH 33/37] Initialize hotkey mapper after window is loaded --- Flow.Launcher/App.xaml.cs | 2 -- Flow.Launcher/MainWindow.xaml.cs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index f484d4dba..81938612c 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -179,8 +179,6 @@ namespace Flow.Launcher Current.MainWindow = _mainWindow; Current.MainWindow.Title = Constant.FlowLauncher; - HotKeyMapper.Initialize(); - // main windows needs initialized before theme change because of blur settings Ioc.Default.GetRequiredService().ChangeTheme(); diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index c62606743..6173fd8ce 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -173,6 +173,9 @@ namespace Flow.Launcher // Without this part, when shown for the first time, switching the context menu does not move the cursor to the end. _viewModel.QueryTextCursorMovedToEnd = false; + // Initialize hotkey mapper after window shown or hiden the first it is loaded + HotKeyMapper.Initialize(); + // View model property changed event _viewModel.PropertyChanged += (o, e) => { From 4affbe3d0159e07a4ebefe4bad1d20426112475c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 13:04:37 +0800 Subject: [PATCH 34/37] Wait image cache storage --- Flow.Launcher/MainWindow.xaml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 6173fd8ce..ccb8d4db6 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -18,6 +18,7 @@ using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.ViewModel; @@ -292,6 +293,7 @@ namespace Flow.Launcher _notifyIcon.Visible = false; App.API.SaveAppAllSettings(); e.Cancel = true; + await ImageLoader.WaitSaveAsync(); await PluginManager.DisposePluginsAsync(); Notification.Uninstall(); // After plugins are all disposed, we can close the main window From a3ea61589d2424dd5461c25234636d9912f12d09 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 13:05:04 +0800 Subject: [PATCH 35/37] Fix build issue --- Flow.Launcher/MainWindow.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index ccb8d4db6..29b4c203f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -16,6 +16,7 @@ using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; +using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Image; From 7723c4454b8321fd120e7c347692172d56dff6f0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 13:08:39 +0800 Subject: [PATCH 36/37] Improve code comments --- Flow.Launcher/MainWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 29b4c203f..30afe67a1 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -175,7 +175,7 @@ namespace Flow.Launcher // Without this part, when shown for the first time, switching the context menu does not move the cursor to the end. _viewModel.QueryTextCursorMovedToEnd = false; - // Initialize hotkey mapper after window shown or hiden the first it is loaded + // Initialize hotkey mapper after window is loaded HotKeyMapper.Initialize(); // View model property changed event From 08230df5c7aa7e062d1760595577e9c1c4bbd552 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 5 Apr 2025 16:38:06 +0800 Subject: [PATCH 37/37] Code quality --- Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 1ee033821..c8d3ffbc4 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -5,12 +5,10 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; -using static Flow.Launcher.Infrastructure.Http.Http; namespace Flow.Launcher.Infrastructure.Image { @@ -28,7 +26,6 @@ namespace Flow.Launcher.Infrastructure.Image public const int SmallIconSize = 64; public const int FullIconSize = 256; - private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" }; public static async Task InitializeAsync() @@ -183,7 +180,7 @@ namespace Flow.Launcher.Infrastructure.Image private static async Task LoadRemoteImageAsync(bool loadFullImage, Uri uriResult) { // Download image from url - await using var resp = await GetStreamAsync(uriResult); + await using var resp = await Http.Http.GetStreamAsync(uriResult); await using var buffer = new MemoryStream(); await resp.CopyToAsync(buffer); buffer.Seek(0, SeekOrigin.Begin);