From 949344a51e9062828e800149adb91165cc8882c3 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 17:32:33 +0800
Subject: [PATCH 01/36] Add internal model for plugin management
---
.../UserSettings/Settings.cs | 2 +
Flow.Launcher/Languages/en.xaml | 18 ++
.../Views/SettingsPaneGeneral.xaml | 11 +
.../ViewModel/PluginStoreItemViewModel.cs | 270 ++++++++++++++++--
Flow.Launcher/ViewModel/PluginViewModel.cs | 23 +-
5 files changed, 280 insertions(+), 44 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index ce1269a2995..024e727ce7f 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -176,6 +176,8 @@ public bool ShowHomePage
public bool ShowHistoryResultsForHomePage { get; set; } = false;
public int MaxHistoryResultsToShowForHomePage { get; set; } = 5;
+ public bool AutoRestartAfterChanging { get; set; } = false;
+
public int CustomExplorerIndex { get; set; } = 0;
[JsonIgnore]
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 22ab2016cc0..24f74e15daa 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -131,6 +131,8 @@
Show History Results in Home Page
Maximum History Results Shown in Home Page
This can only be edited if plugin supports Home feature and Home Page is enabled.
+ Automatically restart after changing plugins
+ Automatically restart Flow Launcher after installing/uninstalling/updating plugins
Search Plugin
@@ -184,6 +186,22 @@
New Version
This plugin has been updated within the last 7 days
New Update is Available
+ Error installing plugin
+ Error uninstalling plugin
+ Error updating plugin
+ Keep plugin settings
+ Do you want to keep the settings of the plugin for the next usage?
+ Plugin {0} successfully installed. Please restart Flow.
+ Plugin {0} successfully uninstalled. Please restart Flow.
+ Plugin {0} successfully updated. Please restart Flow.
+ Plugin install
+ {0} by {1} {2}{2}Would you like to install this plugin?
+ Plugin uninstall
+ {0} by {1} {2}{2}Would you like to uninstall this plugin?
+ Plugin udpate
+ {0} by {1} {2}{2}Would you like to update this plugin?
+ Downloading plugin
+ Automatically restart after installing/uninstalling/updating plugins in plugin store
Theme
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index c0c5613de04..452e026d703 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -202,6 +202,17 @@
+
+
+
+
PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7");
+ private static readonly string ClassName = nameof(PluginStoreItemViewModel);
+
+ private static readonly Settings Settings = Ioc.Default.GetRequiredService();
+
+ private readonly UserPlugin _newPlugin;
+ private readonly PluginPair _oldPluginPair;
+
public PluginStoreItemViewModel(UserPlugin plugin)
{
- _plugin = plugin;
+ _newPlugin = plugin;
+ _oldPluginPair = PluginManager.GetPluginForId(plugin.ID);
}
- private UserPlugin _plugin;
-
- public string ID => _plugin.ID;
- public string Name => _plugin.Name;
- public string Description => _plugin.Description;
- public string Author => _plugin.Author;
- public string Version => _plugin.Version;
- public string Language => _plugin.Language;
- public string Website => _plugin.Website;
- public string UrlDownload => _plugin.UrlDownload;
- public string UrlSourceCode => _plugin.UrlSourceCode;
- public string IcoPath => _plugin.IcoPath;
+ public string ID => _newPlugin.ID;
+ public string Name => _newPlugin.Name;
+ public string Description => _newPlugin.Description;
+ public string Author => _newPlugin.Author;
+ public string Version => _newPlugin.Version;
+ public string Language => _newPlugin.Language;
+ public string Website => _newPlugin.Website;
+ public string UrlDownload => _newPlugin.UrlDownload;
+ public string UrlSourceCode => _newPlugin.UrlSourceCode;
+ public string IcoPath => _newPlugin.IcoPath;
- public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null;
- public bool LabelUpdate => LabelInstalled && new Version(_plugin.Version) > new Version(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version);
+ public bool LabelInstalled => _oldPluginPair != null;
+ public bool LabelUpdate => LabelInstalled && new Version(_newPlugin.Version) > new Version(_oldPluginPair.Metadata.Version);
internal const string None = "None";
internal const string RecentlyUpdated = "RecentlyUpdated";
@@ -41,15 +51,15 @@ public string Category
get
{
string category = None;
- if (DateTime.Now - _plugin.LatestReleaseDate < TimeSpan.FromDays(7))
+ if (DateTime.Now - _newPlugin.LatestReleaseDate < TimeSpan.FromDays(7))
{
category = RecentlyUpdated;
}
- if (DateTime.Now - _plugin.DateAdded < TimeSpan.FromDays(7))
+ if (DateTime.Now - _newPlugin.DateAdded < TimeSpan.FromDays(7))
{
category = NewRelease;
}
- if (PluginManager.GetPluginForId(_plugin.ID) != null)
+ if (_oldPluginPair != null)
{
category = Installed;
}
@@ -59,11 +69,223 @@ public string Category
}
[RelayCommand]
- private void ShowCommandQuery(string action)
+ private async Task ShowCommandQueryAsync(string action)
+ {
+ switch (action)
+ {
+ case "install":
+ await InstallPluginAsync(_newPlugin);
+ break;
+ case "uninstall":
+ await UninstallPluginAsync(_oldPluginPair.Metadata);
+ break;
+ case "update":
+ await UpdatePluginAsync(_newPlugin, _oldPluginPair.Metadata);
+ break;
+ }
+ }
+
+ internal static async Task InstallPluginAsync(UserPlugin newPlugin)
+ {
+ if (App.API.ShowMsgBox(
+ string.Format(
+ App.API.GetTranslation("InstallPromptSubtitle"),
+ newPlugin.Name, newPlugin.Author, Environment.NewLine),
+ App.API.GetTranslation("InstallPromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ try
+ {
+ // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
+ var downloadFilename = string.IsNullOrEmpty(newPlugin.Version)
+ ? $"{newPlugin.Name}-{Guid.NewGuid()}.zip"
+ : $"{newPlugin.Name}-{newPlugin.Version}.zip";
+
+ var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
+
+ using var cts = new CancellationTokenSource();
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ await DownloadFileAsync(
+ $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ newPlugin.UrlDownload, filePath, cts);
+ }
+ else
+ {
+ filePath = newPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
+ }
+
+ App.API.InstallPlugin(newPlugin, filePath);
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to install plugin", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ErrorInstallingPlugin"));
+ }
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ App.API.RestartApp();
+ }
+ else
+ {
+ App.API.ShowMsg(
+ App.API.GetTranslation("installbtn"),
+ string.Format(
+ App.API.GetTranslation(
+ "InstallSuccessNoRestart"),
+ newPlugin.Name));
+ }
+ }
+
+ internal static async Task UninstallPluginAsync(PluginMetadata oldPlugin)
{
- var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty;
- App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}");
- App.API.ShowMainWindow();
+ if (App.API.ShowMsgBox(
+ string.Format(
+ App.API.GetTranslation("UninstallPromptSubtitle"),
+ oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
+ App.API.GetTranslation("UninstallPromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ var removePluginSettings = App.API.ShowMsgBox(
+ App.API.GetTranslation("KeepPluginSettingsSubtitle"),
+ App.API.GetTranslation("KeepPluginSettingsTitle"),
+ button: MessageBoxButton.YesNo) == MessageBoxResult.No;
+
+ try
+ {
+ await App.API.UninstallPluginAsync(oldPlugin, removePluginSettings);
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to uninstall plugin", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ErrorUninstallingPlugin"));
+ }
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ App.API.RestartApp();
+ }
+ else
+ {
+ App.API.ShowMsg(
+ App.API.GetTranslation("uninstallbtn"),
+ string.Format(
+ App.API.GetTranslation(
+ "UninstallSuccessNoRestart"),
+ oldPlugin.Name));
+ }
+ }
+
+ internal static async Task UpdatePluginAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
+ {
+ if (App.API.ShowMsgBox(
+ string.Format(
+ App.API.GetTranslation("UpdatePromptSubtitle"),
+ oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
+ App.API.GetTranslation("UpdatePromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ try
+ {
+ var filePath = Path.Combine(Path.GetTempPath(), $"{newPlugin.Name}-{newPlugin.Version}.zip");
+
+ using var cts = new CancellationTokenSource();
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ await DownloadFileAsync(
+ $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ newPlugin.UrlDownload, filePath, cts);
+ }
+ else
+ {
+ filePath = newPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ await App.API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
+ }
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to update plugin", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ErrorUpdatingPlugin"));
+ }
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ App.API.RestartApp();
+ }
+ else
+ {
+ App.API.ShowMsg(
+ App.API.GetTranslation("updatebtn"),
+ string.Format(
+ App.API.GetTranslation(
+ "UpdateSuccessNoRestart"),
+ newPlugin.Name));
+ }
+ }
+
+ private static async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
+ {
+ if (deleteFile && File.Exists(filePath))
+ File.Delete(filePath);
+
+ if (showProgress)
+ {
+ var exceptionHappened = false;
+ await App.API.ShowProgressBoxAsync(prgBoxTitle,
+ async (reportProgress) =>
+ {
+ if (reportProgress == null)
+ {
+ // when reportProgress is null, it means there is expcetion with the progress box
+ // so we record it with exceptionHappened and return so that progress box will close instantly
+ exceptionHappened = true;
+ return;
+ }
+ else
+ {
+ await App.API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
+ }
+ }, cts.Cancel);
+
+ // if exception happened while downloading and user does not cancel downloading,
+ // we need to redownload the plugin
+ if (exceptionHappened && (!cts.IsCancellationRequested))
+ await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ }
+ else
+ {
+ await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ }
}
}
}
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index 01fa3d20326..bda05a02d80 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -1,5 +1,4 @@
-using System.Linq;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -32,21 +31,6 @@ public PluginPair PluginPair
}
}
- private static string PluginManagerActionKeyword
- {
- get
- {
- var keyword = PluginManager
- .GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")
- .Metadata.ActionKeywords.FirstOrDefault();
- return keyword switch
- {
- null or "*" => string.Empty,
- _ => keyword
- };
- }
- }
-
private async Task LoadIconAsync()
{
Image = await App.API.LoadImageAsync(PluginPair.Metadata.IcoPath);
@@ -186,10 +170,9 @@ private void OpenSourceCodeLink()
}
[RelayCommand]
- private void OpenDeletePluginWindow()
+ private async Task OpenDeletePluginWindowAsync()
{
- App.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true);
- App.API.ShowMainWindow();
+ await PluginStoreItemViewModel.UninstallPluginAsync(PluginPair.Metadata);
}
[RelayCommand]
From 76736b785091873534770d4806572f2641f20adf Mon Sep 17 00:00:00 2001
From: Jack Ye <1160210343@qq.com>
Date: Thu, 22 May 2025 17:37:27 +0800
Subject: [PATCH 02/36] Fix typo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
Flow.Launcher/Languages/en.xaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 24f74e15daa..2166bdd8c30 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -198,7 +198,7 @@
{0} by {1} {2}{2}Would you like to install this plugin?
Plugin uninstall
{0} by {1} {2}{2}Would you like to uninstall this plugin?
- Plugin udpate
+ Plugin update
{0} by {1} {2}{2}Would you like to update this plugin?
Downloading plugin
Automatically restart after installing/uninstalling/updating plugins in plugin store
From c6c7ff882e6745216c828559191966753553e839 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 17:40:26 +0800
Subject: [PATCH 03/36] Handle default
---
Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index 3e823d635b4..a69c0dbd795 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -82,6 +82,8 @@ private async Task ShowCommandQueryAsync(string action)
case "update":
await UpdatePluginAsync(_newPlugin, _oldPluginPair.Metadata);
break;
+ default:
+ break;
}
}
From 6044f87e806c97cd96f9d62beb6fb3a868ba1efe Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 17:41:13 +0800
Subject: [PATCH 04/36] Do not restart on failure
---
Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index a69c0dbd795..6b2cf6eed50 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -142,6 +142,7 @@ await DownloadFileAsync(
{
App.API.LogException(ClassName, "Failed to install plugin", e);
App.API.ShowMsgError(App.API.GetTranslation("ErrorInstallingPlugin"));
+ return; // don’t restart on failure
}
if (Settings.AutoRestartAfterChanging)
@@ -181,6 +182,7 @@ internal static async Task UninstallPluginAsync(PluginMetadata oldPlugin)
{
App.API.LogException(ClassName, "Failed to uninstall plugin", e);
App.API.ShowMsgError(App.API.GetTranslation("ErrorUninstallingPlugin"));
+ return; // don’t restart on failure
}
if (Settings.AutoRestartAfterChanging)
@@ -238,6 +240,7 @@ await DownloadFileAsync(
{
App.API.LogException(ClassName, "Failed to update plugin", e);
App.API.ShowMsgError(App.API.GetTranslation("ErrorUpdatingPlugin"));
+ return; // don’t restart on failure
}
if (Settings.AutoRestartAfterChanging)
From 383c0aeffc9afc1351f3c008d62aa0915ddc0da4 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 13:41:59 +0800
Subject: [PATCH 05/36] Improve code quality
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 209 +++++++++++++++++
.../ViewModel/PluginStoreItemViewModel.cs | 221 +-----------------
Flow.Launcher/ViewModel/PluginViewModel.cs | 2 +-
3 files changed, 213 insertions(+), 219 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index aae8dd76419..5b14ad0b762 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -6,6 +6,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
@@ -24,6 +25,8 @@ public static class PluginManager
{
private static readonly string ClassName = nameof(PluginManager);
+ private static readonly Settings FlowSettings = Ioc.Default.GetRequiredService();
+
private static IEnumerable _contextMenuPlugins;
private static IEnumerable _homePlugins;
@@ -547,6 +550,177 @@ public static async Task UninstallPluginAsync(PluginMetadata plugin, bool remove
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
}
+ public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
+ {
+ if (API.ShowMsgBox(
+ string.Format(
+ API.GetTranslation("InstallPromptSubtitle"),
+ newPlugin.Name, newPlugin.Author, Environment.NewLine),
+ API.GetTranslation("InstallPromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ try
+ {
+ // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
+ var downloadFilename = string.IsNullOrEmpty(newPlugin.Version)
+ ? $"{newPlugin.Name}-{Guid.NewGuid()}.zip"
+ : $"{newPlugin.Name}-{newPlugin.Version}.zip";
+
+ var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
+
+ using var cts = new CancellationTokenSource();
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ await DownloadFileAsync(
+ $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ newPlugin.UrlDownload, filePath, cts);
+ }
+ else
+ {
+ filePath = newPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
+ }
+
+ API.InstallPlugin(newPlugin, filePath);
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, "Failed to install plugin", e);
+ API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin"));
+ return; // don’t restart on failure
+ }
+
+ if (FlowSettings.AutoRestartAfterChanging)
+ {
+ API.RestartApp();
+ }
+ else
+ {
+ API.ShowMsg(
+ API.GetTranslation("installbtn"),
+ string.Format(
+ API.GetTranslation(
+ "InstallSuccessNoRestart"),
+ newPlugin.Name));
+ }
+ }
+
+ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
+ {
+ if (API.ShowMsgBox(
+ string.Format(
+ API.GetTranslation("UninstallPromptSubtitle"),
+ oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
+ API.GetTranslation("UninstallPromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ var removePluginSettings = API.ShowMsgBox(
+ API.GetTranslation("KeepPluginSettingsSubtitle"),
+ API.GetTranslation("KeepPluginSettingsTitle"),
+ button: MessageBoxButton.YesNo) == MessageBoxResult.No;
+
+ try
+ {
+ await API.UninstallPluginAsync(oldPlugin, removePluginSettings);
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, "Failed to uninstall plugin", e);
+ API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin"));
+ return; // don’t restart on failure
+ }
+
+ if (FlowSettings.AutoRestartAfterChanging)
+ {
+ API.RestartApp();
+ }
+ else
+ {
+ API.ShowMsg(
+ API.GetTranslation("uninstallbtn"),
+ string.Format(
+ API.GetTranslation(
+ "UninstallSuccessNoRestart"),
+ oldPlugin.Name));
+ }
+ }
+
+ public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
+ {
+ if (API.ShowMsgBox(
+ string.Format(
+ API.GetTranslation("UpdatePromptSubtitle"),
+ oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
+ API.GetTranslation("UpdatePromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ try
+ {
+ var filePath = Path.Combine(Path.GetTempPath(), $"{newPlugin.Name}-{newPlugin.Version}.zip");
+
+ using var cts = new CancellationTokenSource();
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ await DownloadFileAsync(
+ $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ newPlugin.UrlDownload, filePath, cts);
+ }
+ else
+ {
+ filePath = newPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
+ }
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, "Failed to update plugin", e);
+ API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
+ return; // don’t restart on failure
+ }
+
+ if (FlowSettings.AutoRestartAfterChanging)
+ {
+ API.RestartApp();
+ }
+ else
+ {
+ API.ShowMsg(
+ API.GetTranslation("updatebtn"),
+ string.Format(
+ API.GetTranslation(
+ "UpdateSuccessNoRestart"),
+ newPlugin.Name));
+ }
+ }
+
#endregion
#region Internal functions
@@ -694,6 +868,41 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
}
}
+ internal static async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
+ {
+ if (deleteFile && File.Exists(filePath))
+ File.Delete(filePath);
+
+ if (showProgress)
+ {
+ var exceptionHappened = false;
+ await API.ShowProgressBoxAsync(prgBoxTitle,
+ async (reportProgress) =>
+ {
+ if (reportProgress == null)
+ {
+ // when reportProgress is null, it means there is expcetion with the progress box
+ // so we record it with exceptionHappened and return so that progress box will close instantly
+ exceptionHappened = true;
+ return;
+ }
+ else
+ {
+ await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
+ }
+ }, cts.Cancel);
+
+ // if exception happened while downloading and user does not cancel downloading,
+ // we need to redownload the plugin
+ if (exceptionHappened && (!cts.IsCancellationRequested))
+ await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ }
+ else
+ {
+ await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ }
+ }
+
#endregion
}
}
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index 6b2cf6eed50..a504b7a051b 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -1,12 +1,7 @@
using System;
-using System.IO;
-using System.Threading;
using System.Threading.Tasks;
-using System.Windows;
-using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Version = SemanticVersioning.Version;
@@ -14,10 +9,6 @@ namespace Flow.Launcher.ViewModel
{
public partial class PluginStoreItemViewModel : BaseModel
{
- private static readonly string ClassName = nameof(PluginStoreItemViewModel);
-
- private static readonly Settings Settings = Ioc.Default.GetRequiredService();
-
private readonly UserPlugin _newPlugin;
private readonly PluginPair _oldPluginPair;
@@ -74,223 +65,17 @@ private async Task ShowCommandQueryAsync(string action)
switch (action)
{
case "install":
- await InstallPluginAsync(_newPlugin);
+ await PluginManager.InstallPluginAndCheckRestartAsync(_newPlugin);
break;
case "uninstall":
- await UninstallPluginAsync(_oldPluginPair.Metadata);
+ await PluginManager.UninstallPluginAndCheckRestartAsync(_oldPluginPair.Metadata);
break;
case "update":
- await UpdatePluginAsync(_newPlugin, _oldPluginPair.Metadata);
+ await PluginManager.UpdatePluginAndCheckRestartAsync(_newPlugin, _oldPluginPair.Metadata);
break;
default:
break;
}
}
-
- internal static async Task InstallPluginAsync(UserPlugin newPlugin)
- {
- if (App.API.ShowMsgBox(
- string.Format(
- App.API.GetTranslation("InstallPromptSubtitle"),
- newPlugin.Name, newPlugin.Author, Environment.NewLine),
- App.API.GetTranslation("InstallPromptTitle"),
- button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
-
- try
- {
- // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
- var downloadFilename = string.IsNullOrEmpty(newPlugin.Version)
- ? $"{newPlugin.Name}-{Guid.NewGuid()}.zip"
- : $"{newPlugin.Name}-{newPlugin.Version}.zip";
-
- var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
-
- using var cts = new CancellationTokenSource();
-
- if (!newPlugin.IsFromLocalInstallPath)
- {
- await DownloadFileAsync(
- $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
- newPlugin.UrlDownload, filePath, cts);
- }
- else
- {
- filePath = newPlugin.LocalInstallPath;
- }
-
- // check if user cancelled download before installing plugin
- if (cts.IsCancellationRequested)
- {
- return;
- }
- else
- {
- if (!File.Exists(filePath))
- {
- throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
- }
-
- App.API.InstallPlugin(newPlugin, filePath);
-
- if (!newPlugin.IsFromLocalInstallPath)
- {
- File.Delete(filePath);
- }
- }
- }
- catch (Exception e)
- {
- App.API.LogException(ClassName, "Failed to install plugin", e);
- App.API.ShowMsgError(App.API.GetTranslation("ErrorInstallingPlugin"));
- return; // don’t restart on failure
- }
-
- if (Settings.AutoRestartAfterChanging)
- {
- App.API.RestartApp();
- }
- else
- {
- App.API.ShowMsg(
- App.API.GetTranslation("installbtn"),
- string.Format(
- App.API.GetTranslation(
- "InstallSuccessNoRestart"),
- newPlugin.Name));
- }
- }
-
- internal static async Task UninstallPluginAsync(PluginMetadata oldPlugin)
- {
- if (App.API.ShowMsgBox(
- string.Format(
- App.API.GetTranslation("UninstallPromptSubtitle"),
- oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
- App.API.GetTranslation("UninstallPromptTitle"),
- button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
-
- var removePluginSettings = App.API.ShowMsgBox(
- App.API.GetTranslation("KeepPluginSettingsSubtitle"),
- App.API.GetTranslation("KeepPluginSettingsTitle"),
- button: MessageBoxButton.YesNo) == MessageBoxResult.No;
-
- try
- {
- await App.API.UninstallPluginAsync(oldPlugin, removePluginSettings);
- }
- catch (Exception e)
- {
- App.API.LogException(ClassName, "Failed to uninstall plugin", e);
- App.API.ShowMsgError(App.API.GetTranslation("ErrorUninstallingPlugin"));
- return; // don’t restart on failure
- }
-
- if (Settings.AutoRestartAfterChanging)
- {
- App.API.RestartApp();
- }
- else
- {
- App.API.ShowMsg(
- App.API.GetTranslation("uninstallbtn"),
- string.Format(
- App.API.GetTranslation(
- "UninstallSuccessNoRestart"),
- oldPlugin.Name));
- }
- }
-
- internal static async Task UpdatePluginAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
- {
- if (App.API.ShowMsgBox(
- string.Format(
- App.API.GetTranslation("UpdatePromptSubtitle"),
- oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
- App.API.GetTranslation("UpdatePromptTitle"),
- button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
-
- try
- {
- var filePath = Path.Combine(Path.GetTempPath(), $"{newPlugin.Name}-{newPlugin.Version}.zip");
-
- using var cts = new CancellationTokenSource();
-
- if (!newPlugin.IsFromLocalInstallPath)
- {
- await DownloadFileAsync(
- $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
- newPlugin.UrlDownload, filePath, cts);
- }
- else
- {
- filePath = newPlugin.LocalInstallPath;
- }
-
- // check if user cancelled download before installing plugin
- if (cts.IsCancellationRequested)
- {
- return;
- }
- else
- {
- await App.API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
- }
- }
- catch (Exception e)
- {
- App.API.LogException(ClassName, "Failed to update plugin", e);
- App.API.ShowMsgError(App.API.GetTranslation("ErrorUpdatingPlugin"));
- return; // don’t restart on failure
- }
-
- if (Settings.AutoRestartAfterChanging)
- {
- App.API.RestartApp();
- }
- else
- {
- App.API.ShowMsg(
- App.API.GetTranslation("updatebtn"),
- string.Format(
- App.API.GetTranslation(
- "UpdateSuccessNoRestart"),
- newPlugin.Name));
- }
- }
-
- private static async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
- {
- if (deleteFile && File.Exists(filePath))
- File.Delete(filePath);
-
- if (showProgress)
- {
- var exceptionHappened = false;
- await App.API.ShowProgressBoxAsync(prgBoxTitle,
- async (reportProgress) =>
- {
- if (reportProgress == null)
- {
- // when reportProgress is null, it means there is expcetion with the progress box
- // so we record it with exceptionHappened and return so that progress box will close instantly
- exceptionHappened = true;
- return;
- }
- else
- {
- await App.API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
- }
- }, cts.Cancel);
-
- // if exception happened while downloading and user does not cancel downloading,
- // we need to redownload the plugin
- if (exceptionHappened && (!cts.IsCancellationRequested))
- await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
- }
- else
- {
- await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
- }
- }
}
}
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index bda05a02d80..f902fb03775 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -172,7 +172,7 @@ private void OpenSourceCodeLink()
[RelayCommand]
private async Task OpenDeletePluginWindowAsync()
{
- await PluginStoreItemViewModel.UninstallPluginAsync(PluginPair.Metadata);
+ await PluginManager.UninstallPluginAndCheckRestartAsync(PluginPair.Metadata);
}
[RelayCommand]
From 6bf7f00f0a7c268ff2e5c62bcbe791ff84328157 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 1 Jun 2025 16:28:16 +0800
Subject: [PATCH 06/36] Add unknown source warning setting
---
.../UserSettings/Settings.cs | 1 +
Flow.Launcher/Languages/en.xaml | 2 ++
.../Views/SettingsPaneGeneral.xaml | 31 +++++++++++++------
3 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 0b2b042d4a3..9f8e510470a 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -191,6 +191,7 @@ public bool ShowHistoryResultsForHomePage
public int MaxHistoryResultsToShowForHomePage { get; set; } = 5;
public bool AutoRestartAfterChanging { get; set; } = false;
+ public bool ShowUnknownSourceWarning { get; set; } = true;
public int CustomExplorerIndex { get; set; } = 0;
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 7f00926f186..2b42b8f8499 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -133,6 +133,8 @@
This can only be edited if plugin supports Home feature and Home Page is enabled.
Automatically restart after changing plugins
Automatically restart Flow Launcher after installing/uninstalling/updating plugins
+ Show unknown source warning
+ Show warning when installing plugins from unknown sources
Search Plugin
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index 452e026d703..1966c4c0d82 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -202,16 +202,27 @@
-
-
-
+
+
+
+
+
+
+
+
+
Date: Mon, 9 Jun 2025 20:28:18 +0800
Subject: [PATCH 07/36] Support installing from local path
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 55 +++++++++++++++++++
Flow.Launcher/Languages/en.xaml | 6 ++
.../SettingsPanePluginStoreViewModel.cs | 35 +++++++++++-
.../Views/SettingsPanePluginStore.xaml | 7 +++
4 files changed, 102 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index ef831e940d9..f7a2461ab3c 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Text.Json;
using System.Threading;
@@ -633,6 +634,42 @@ await DownloadFileAsync(
}
}
+ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
+ {
+ UserPlugin plugin;
+ try
+ {
+ using ZipArchive archive = ZipFile.OpenRead(filePath);
+ var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
+ throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
+ var pluginJsonEntry = archive.GetEntry(pluginJsonPath.ToString()) ??
+ throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
+
+ using Stream stream = pluginJsonEntry.Open();
+ plugin = JsonSerializer.Deserialize(stream);
+ plugin.IcoPath = "Images\\zipfolder.png";
+ plugin.LocalInstallPath = filePath;
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, "Failed to validate zip file", e);
+ API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson"));
+ return;
+ }
+
+ if (FlowSettings.ShowUnknownSourceWarning)
+ {
+ if (!InstallSourceKnown(plugin.Website)
+ && API.ShowMsgBox(string.Format(
+ API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine),
+ API.GetTranslation("InstallFromUnknownSourceTitle"),
+ MessageBoxButton.YesNo) == MessageBoxResult.No)
+ return;
+ }
+
+ await InstallPluginAndCheckRestartAsync(plugin);
+ }
+
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
{
if (API.ShowMsgBox(
@@ -913,6 +950,24 @@ await API.ShowProgressBoxAsync(prgBoxTitle,
}
}
+ private static bool InstallSourceKnown(string url)
+ {
+ var pieces = url.Split('/');
+
+ if (pieces.Length < 4)
+ return false;
+
+ var author = pieces[3];
+ var acceptedSource = "https://github.com";
+ var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
+
+ return url.StartsWith(acceptedSource) &&
+ API.GetAllPlugins().Any(x =>
+ !string.IsNullOrEmpty(x.Metadata.Website) &&
+ x.Metadata.Website.StartsWith(constructedUrlPart)
+ );
+ }
+
#endregion
}
}
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index b3fdd68928a..6ce5d17d052 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -204,6 +204,12 @@
{0} by {1} {2}{2}Would you like to update this plugin?
Downloading plugin
Automatically restart after installing/uninstalling/updating plugins in plugin store
+ Zip file does not have a valid plugin.json configuration
+ Installing from an unknown source
+ This plugin is from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning in general section of setting window)
+ Zip files
+ Please select zip file
+ Install plugin from local path
Theme
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index 07df0682dbb..b9b7c12fa1e 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -1,7 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@@ -96,6 +99,36 @@ private async Task RefreshExternalPluginsAsync()
}
}
+ [RelayCommand]
+ private async Task InstallPluginAsync()
+ {
+ var file = GetFileFromDialog(
+ App.API.GetTranslation("SelectZipFile"),
+ $"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip");
+
+ if (!string.IsNullOrEmpty(file))
+ await PluginManager.InstallPluginAndCheckRestartAsync(file);
+ }
+
+ private static string GetFileFromDialog(string title, string filter = "")
+ {
+ var dlg = new OpenFileDialog
+ {
+ InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads",
+ Multiselect = false,
+ CheckFileExists = true,
+ CheckPathExists = true,
+ Title = title,
+ Filter = filter
+ };
+
+ return dlg.ShowDialog() switch
+ {
+ DialogResult.OK => dlg.FileName,
+ _ => string.Empty
+ };
+ }
+
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
{
// Check plugin language
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
index 9312b0c2dfd..68f78d46c2e 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
@@ -92,6 +92,13 @@
+
Date: Sun, 29 Jun 2025 15:47:46 +0800
Subject: [PATCH 08/36] Add type for card elements inside card group
---
Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index d831774fb2c..ac27c3b40a9 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -229,7 +229,8 @@
+ Sub="{DynamicResource autoRestartAfterChangingToolTip}"
+ Type="First">
+ Sub="{DynamicResource showUnknownSourceWarningToolTip}"
+ Type="Last">
Date: Sun, 29 Jun 2025 15:48:08 +0800
Subject: [PATCH 09/36] Move codes to new place
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 264 ----------------
.../Helper/PluginInstallationHelper.cs | 283 ++++++++++++++++++
.../SettingsPanePluginStoreViewModel.cs | 4 +-
.../ViewModel/PluginStoreItemViewModel.cs | 7 +-
Flow.Launcher/ViewModel/PluginViewModel.cs | 3 +-
5 files changed, 291 insertions(+), 270 deletions(-)
create mode 100644 Flow.Launcher/Helper/PluginInstallationHelper.cs
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index f7a2461ab3c..9b525f331d2 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -2,12 +2,10 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
-using System.IO.Compression;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
@@ -26,8 +24,6 @@ public static class PluginManager
{
private static readonly string ClassName = nameof(PluginManager);
- private static readonly Settings FlowSettings = Ioc.Default.GetRequiredService();
-
private static IEnumerable _contextMenuPlugins;
private static IEnumerable _homePlugins;
@@ -561,213 +557,6 @@ public static async Task UninstallPluginAsync(PluginMetadata plugin, bool remove
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
}
- public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
- {
- if (API.ShowMsgBox(
- string.Format(
- API.GetTranslation("InstallPromptSubtitle"),
- newPlugin.Name, newPlugin.Author, Environment.NewLine),
- API.GetTranslation("InstallPromptTitle"),
- button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
-
- try
- {
- // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
- var downloadFilename = string.IsNullOrEmpty(newPlugin.Version)
- ? $"{newPlugin.Name}-{Guid.NewGuid()}.zip"
- : $"{newPlugin.Name}-{newPlugin.Version}.zip";
-
- var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
-
- using var cts = new CancellationTokenSource();
-
- if (!newPlugin.IsFromLocalInstallPath)
- {
- await DownloadFileAsync(
- $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
- newPlugin.UrlDownload, filePath, cts);
- }
- else
- {
- filePath = newPlugin.LocalInstallPath;
- }
-
- // check if user cancelled download before installing plugin
- if (cts.IsCancellationRequested)
- {
- return;
- }
- else
- {
- if (!File.Exists(filePath))
- {
- throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
- }
-
- API.InstallPlugin(newPlugin, filePath);
-
- if (!newPlugin.IsFromLocalInstallPath)
- {
- File.Delete(filePath);
- }
- }
- }
- catch (Exception e)
- {
- API.LogException(ClassName, "Failed to install plugin", e);
- API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin"));
- return; // don’t restart on failure
- }
-
- if (FlowSettings.AutoRestartAfterChanging)
- {
- API.RestartApp();
- }
- else
- {
- API.ShowMsg(
- API.GetTranslation("installbtn"),
- string.Format(
- API.GetTranslation(
- "InstallSuccessNoRestart"),
- newPlugin.Name));
- }
- }
-
- public static async Task InstallPluginAndCheckRestartAsync(string filePath)
- {
- UserPlugin plugin;
- try
- {
- using ZipArchive archive = ZipFile.OpenRead(filePath);
- var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
- throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
- var pluginJsonEntry = archive.GetEntry(pluginJsonPath.ToString()) ??
- throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
-
- using Stream stream = pluginJsonEntry.Open();
- plugin = JsonSerializer.Deserialize(stream);
- plugin.IcoPath = "Images\\zipfolder.png";
- plugin.LocalInstallPath = filePath;
- }
- catch (Exception e)
- {
- API.LogException(ClassName, "Failed to validate zip file", e);
- API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson"));
- return;
- }
-
- if (FlowSettings.ShowUnknownSourceWarning)
- {
- if (!InstallSourceKnown(plugin.Website)
- && API.ShowMsgBox(string.Format(
- API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine),
- API.GetTranslation("InstallFromUnknownSourceTitle"),
- MessageBoxButton.YesNo) == MessageBoxResult.No)
- return;
- }
-
- await InstallPluginAndCheckRestartAsync(plugin);
- }
-
- public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
- {
- if (API.ShowMsgBox(
- string.Format(
- API.GetTranslation("UninstallPromptSubtitle"),
- oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
- API.GetTranslation("UninstallPromptTitle"),
- button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
-
- var removePluginSettings = API.ShowMsgBox(
- API.GetTranslation("KeepPluginSettingsSubtitle"),
- API.GetTranslation("KeepPluginSettingsTitle"),
- button: MessageBoxButton.YesNo) == MessageBoxResult.No;
-
- try
- {
- await API.UninstallPluginAsync(oldPlugin, removePluginSettings);
- }
- catch (Exception e)
- {
- API.LogException(ClassName, "Failed to uninstall plugin", e);
- API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin"));
- return; // don’t restart on failure
- }
-
- if (FlowSettings.AutoRestartAfterChanging)
- {
- API.RestartApp();
- }
- else
- {
- API.ShowMsg(
- API.GetTranslation("uninstallbtn"),
- string.Format(
- API.GetTranslation(
- "UninstallSuccessNoRestart"),
- oldPlugin.Name));
- }
- }
-
- public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
- {
- if (API.ShowMsgBox(
- string.Format(
- API.GetTranslation("UpdatePromptSubtitle"),
- oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
- API.GetTranslation("UpdatePromptTitle"),
- button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
-
- try
- {
- var filePath = Path.Combine(Path.GetTempPath(), $"{newPlugin.Name}-{newPlugin.Version}.zip");
-
- using var cts = new CancellationTokenSource();
-
- if (!newPlugin.IsFromLocalInstallPath)
- {
- await DownloadFileAsync(
- $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
- newPlugin.UrlDownload, filePath, cts);
- }
- else
- {
- filePath = newPlugin.LocalInstallPath;
- }
-
- // check if user cancelled download before installing plugin
- if (cts.IsCancellationRequested)
- {
- return;
- }
- else
- {
- await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
- }
- }
- catch (Exception e)
- {
- API.LogException(ClassName, "Failed to update plugin", e);
- API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
- return; // don’t restart on failure
- }
-
- if (FlowSettings.AutoRestartAfterChanging)
- {
- API.RestartApp();
- }
- else
- {
- API.ShowMsg(
- API.GetTranslation("updatebtn"),
- string.Format(
- API.GetTranslation(
- "UpdateSuccessNoRestart"),
- newPlugin.Name));
- }
- }
-
#endregion
#region Internal functions
@@ -915,59 +704,6 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
}
}
- internal static async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
- {
- if (deleteFile && File.Exists(filePath))
- File.Delete(filePath);
-
- if (showProgress)
- {
- var exceptionHappened = false;
- await API.ShowProgressBoxAsync(prgBoxTitle,
- async (reportProgress) =>
- {
- if (reportProgress == null)
- {
- // when reportProgress is null, it means there is expcetion with the progress box
- // so we record it with exceptionHappened and return so that progress box will close instantly
- exceptionHappened = true;
- return;
- }
- else
- {
- await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
- }
- }, cts.Cancel);
-
- // if exception happened while downloading and user does not cancel downloading,
- // we need to redownload the plugin
- if (exceptionHappened && (!cts.IsCancellationRequested))
- await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
- }
- else
- {
- await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
- }
- }
-
- private static bool InstallSourceKnown(string url)
- {
- var pieces = url.Split('/');
-
- if (pieces.Length < 4)
- return false;
-
- var author = pieces[3];
- var acceptedSource = "https://github.com";
- var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
-
- return url.StartsWith(acceptedSource) &&
- API.GetAllPlugins().Any(x =>
- !string.IsNullOrEmpty(x.Metadata.Website) &&
- x.Metadata.Website.StartsWith(constructedUrlPart)
- );
- }
-
#endregion
}
}
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher/Helper/PluginInstallationHelper.cs
new file mode 100644
index 00000000000..570c5d34cad
--- /dev/null
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -0,0 +1,283 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.Helper;
+
+///
+/// Helper class for installing, updating, and uninstalling plugins.
+///
+public static class PluginInstallationHelper
+{
+ private static readonly string ClassName = nameof(PluginInstallationHelper);
+
+ private static readonly Settings Settings = Ioc.Default.GetRequiredService();
+
+ public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
+ {
+ if (App.API.ShowMsgBox(
+ string.Format(
+ App.API.GetTranslation("InstallPromptSubtitle"),
+ newPlugin.Name, newPlugin.Author, Environment.NewLine),
+ App.API.GetTranslation("InstallPromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ try
+ {
+ // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
+ var downloadFilename = string.IsNullOrEmpty(newPlugin.Version)
+ ? $"{newPlugin.Name}-{Guid.NewGuid()}.zip"
+ : $"{newPlugin.Name}-{newPlugin.Version}.zip";
+
+ var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
+
+ using var cts = new CancellationTokenSource();
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ await DownloadFileAsync(
+ $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ newPlugin.UrlDownload, filePath, cts);
+ }
+ else
+ {
+ filePath = newPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
+ }
+
+ App.API.InstallPlugin(newPlugin, filePath);
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to install plugin", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ErrorInstallingPlugin"));
+ return; // don’t restart on failure
+ }
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ App.API.RestartApp();
+ }
+ else
+ {
+ App.API.ShowMsg(
+ App.API.GetTranslation("installbtn"),
+ string.Format(
+ App.API.GetTranslation(
+ "InstallSuccessNoRestart"),
+ newPlugin.Name));
+ }
+ }
+
+ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
+ {
+ UserPlugin plugin;
+ try
+ {
+ using ZipArchive archive = ZipFile.OpenRead(filePath);
+ var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
+ throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
+ var pluginJsonEntry = archive.GetEntry(pluginJsonPath.ToString()) ??
+ throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
+
+ using Stream stream = pluginJsonEntry.Open();
+ plugin = JsonSerializer.Deserialize(stream);
+ plugin.IcoPath = "Images\\zipfolder.png";
+ plugin.LocalInstallPath = filePath;
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to validate zip file", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ZipFileNotHavePluginJson"));
+ return;
+ }
+
+ if (Settings.ShowUnknownSourceWarning)
+ {
+ if (!InstallSourceKnown(plugin.Website)
+ && App.API.ShowMsgBox(string.Format(
+ App.API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine),
+ App.API.GetTranslation("InstallFromUnknownSourceTitle"),
+ MessageBoxButton.YesNo) == MessageBoxResult.No)
+ return;
+ }
+
+ await InstallPluginAndCheckRestartAsync(plugin);
+ }
+
+ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
+ {
+ if (App.API.ShowMsgBox(
+ string.Format(
+ App.API.GetTranslation("UninstallPromptSubtitle"),
+ oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
+ App.API.GetTranslation("UninstallPromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ var removePluginSettings = App.API.ShowMsgBox(
+ App.API.GetTranslation("KeepPluginSettingsSubtitle"),
+ App.API.GetTranslation("KeepPluginSettingsTitle"),
+ button: MessageBoxButton.YesNo) == MessageBoxResult.No;
+
+ try
+ {
+ await App.API.UninstallPluginAsync(oldPlugin, removePluginSettings);
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to uninstall plugin", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ErrorUninstallingPlugin"));
+ return; // don’t restart on failure
+ }
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ App.API.RestartApp();
+ }
+ else
+ {
+ App.API.ShowMsg(
+ App.API.GetTranslation("uninstallbtn"),
+ string.Format(
+ App.API.GetTranslation(
+ "UninstallSuccessNoRestart"),
+ oldPlugin.Name));
+ }
+ }
+
+ public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
+ {
+ if (App.API.ShowMsgBox(
+ string.Format(
+ App.API.GetTranslation("UpdatePromptSubtitle"),
+ oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
+ App.API.GetTranslation("UpdatePromptTitle"),
+ button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
+
+ try
+ {
+ var filePath = Path.Combine(Path.GetTempPath(), $"{newPlugin.Name}-{newPlugin.Version}.zip");
+
+ using var cts = new CancellationTokenSource();
+
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ await DownloadFileAsync(
+ $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ newPlugin.UrlDownload, filePath, cts);
+ }
+ else
+ {
+ filePath = newPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ await App.API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
+ }
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, "Failed to update plugin", e);
+ App.API.ShowMsgError(App.API.GetTranslation("ErrorUpdatingPlugin"));
+ return; // don’t restart on failure
+ }
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ App.API.RestartApp();
+ }
+ else
+ {
+ App.API.ShowMsg(
+ App.API.GetTranslation("updatebtn"),
+ string.Format(
+ App.API.GetTranslation(
+ "UpdateSuccessNoRestart"),
+ newPlugin.Name));
+ }
+ }
+
+ private static async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
+ {
+ if (deleteFile && File.Exists(filePath))
+ File.Delete(filePath);
+
+ if (showProgress)
+ {
+ var exceptionHappened = false;
+ await App.API.ShowProgressBoxAsync(prgBoxTitle,
+ async (reportProgress) =>
+ {
+ if (reportProgress == null)
+ {
+ // when reportProgress is null, it means there is expcetion with the progress box
+ // so we record it with exceptionHappened and return so that progress box will close instantly
+ exceptionHappened = true;
+ return;
+ }
+ else
+ {
+ await App.API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
+ }
+ }, cts.Cancel);
+
+ // if exception happened while downloading and user does not cancel downloading,
+ // we need to redownload the plugin
+ if (exceptionHappened && (!cts.IsCancellationRequested))
+ await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ }
+ else
+ {
+ await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ }
+ }
+
+ private static bool InstallSourceKnown(string url)
+ {
+ var pieces = url.Split('/');
+
+ if (pieces.Length < 4)
+ return false;
+
+ var author = pieces[3];
+ var acceptedSource = "https://github.com";
+ var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
+
+ return url.StartsWith(acceptedSource) &&
+ App.API.GetAllPlugins().Any(x =>
+ !string.IsNullOrEmpty(x.Metadata.Website) &&
+ x.Metadata.Website.StartsWith(constructedUrlPart)
+ );
+ }
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index b9b7c12fa1e..bce7201b883 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -4,7 +4,7 @@
using System.Threading.Tasks;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
-using Flow.Launcher.Core.Plugin;
+using Flow.Launcher.Helper;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@@ -107,7 +107,7 @@ private async Task InstallPluginAsync()
$"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip");
if (!string.IsNullOrEmpty(file))
- await PluginManager.InstallPluginAndCheckRestartAsync(file);
+ await PluginInstallationHelper.InstallPluginAndCheckRestartAsync(file);
}
private static string GetFileFromDialog(string title, string filter = "")
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index a504b7a051b..f03d2740e33 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
+using Flow.Launcher.Helper;
using Flow.Launcher.Plugin;
using Version = SemanticVersioning.Version;
@@ -65,13 +66,13 @@ private async Task ShowCommandQueryAsync(string action)
switch (action)
{
case "install":
- await PluginManager.InstallPluginAndCheckRestartAsync(_newPlugin);
+ await PluginInstallationHelper.InstallPluginAndCheckRestartAsync(_newPlugin);
break;
case "uninstall":
- await PluginManager.UninstallPluginAndCheckRestartAsync(_oldPluginPair.Metadata);
+ await PluginInstallationHelper.UninstallPluginAndCheckRestartAsync(_oldPluginPair.Metadata);
break;
case "update":
- await PluginManager.UpdatePluginAndCheckRestartAsync(_newPlugin, _oldPluginPair.Metadata);
+ await PluginInstallationHelper.UpdatePluginAndCheckRestartAsync(_newPlugin, _oldPluginPair.Metadata);
break;
default:
break;
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index f902fb03775..131972e8577 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
+using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
@@ -172,7 +173,7 @@ private void OpenSourceCodeLink()
[RelayCommand]
private async Task OpenDeletePluginWindowAsync()
{
- await PluginManager.UninstallPluginAndCheckRestartAsync(PluginPair.Metadata);
+ await PluginInstallationHelper.UninstallPluginAndCheckRestartAsync(PluginPair.Metadata);
}
[RelayCommand]
From 135fd03f88d78e85f2b3471396c58be0c8740de1 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 15:58:52 +0800
Subject: [PATCH 10/36] Improve code quality
---
.../Helper/PluginInstallationHelper.cs | 24 ++++++++-----------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher/Helper/PluginInstallationHelper.cs
index 570c5d34cad..d7ce2934c0a 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -57,19 +57,17 @@ await DownloadFileAsync(
{
return;
}
- else
+
+ if (!File.Exists(filePath))
{
- if (!File.Exists(filePath))
- {
- throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
- }
+ throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
+ }
- App.API.InstallPlugin(newPlugin, filePath);
+ App.API.InstallPlugin(newPlugin, filePath);
- if (!newPlugin.IsFromLocalInstallPath)
- {
- File.Delete(filePath);
- }
+ if (!newPlugin.IsFromLocalInstallPath)
+ {
+ File.Delete(filePath);
}
}
catch (Exception e)
@@ -201,10 +199,8 @@ await DownloadFileAsync(
{
return;
}
- else
- {
- await App.API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
- }
+
+ await App.API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
}
catch (Exception e)
{
From c5dd19ef300acc396c2a0d037ebb52f5aa288864 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 16:03:19 +0800
Subject: [PATCH 11/36] Use Microsoft.Win32.OpenFileDialog instead
---
.../ViewModels/SettingsPanePluginStoreViewModel.cs | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index bce7201b883..2b12ba70fb4 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Helper;
using Flow.Launcher.Plugin;
@@ -112,7 +111,7 @@ private async Task InstallPluginAsync()
private static string GetFileFromDialog(string title, string filter = "")
{
- var dlg = new OpenFileDialog
+ var dlg = new Microsoft.Win32.OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads",
Multiselect = false,
@@ -121,12 +120,11 @@ private static string GetFileFromDialog(string title, string filter = "")
Title = title,
Filter = filter
};
+ var result = dlg.ShowDialog();
+ if (result == true)
+ return dlg.FileName;
- return dlg.ShowDialog() switch
- {
- DialogResult.OK => dlg.FileName,
- _ => string.Empty
- };
+ return string.Empty;
}
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
From 104b4b26805196bfb6c43327dac9bdf05b0d0266 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 16:09:06 +0800
Subject: [PATCH 12/36] Improve code quality
---
Flow.Launcher/Helper/PluginInstallationHelper.cs | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher/Helper/PluginInstallationHelper.cs
index d7ce2934c0a..0d3d2df672c 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -98,9 +98,7 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
try
{
using ZipArchive archive = ZipFile.OpenRead(filePath);
- var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
- throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
- var pluginJsonEntry = archive.GetEntry(pluginJsonPath.ToString()) ??
+ var pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
using Stream stream = pluginJsonEntry.Open();
From 3e9e91d71c8b0f9d63a9c7b97acb89aa6cd4d394 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 16:24:21 +0800
Subject: [PATCH 13/36] Fix possible exception when extracting zip file
---
.../Languages/en.xaml | 3 +++
.../PluginsManager.cs | 26 +++++++++++++++++++
.../Utilities.cs | 4 +--
3 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 573ca90519e..bb0d6e5fb39 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -46,6 +46,9 @@
{0} plugins successfully updated. Please restart Flow.
Plugin {0} has already been modified. Please restart Flow before making any further changes.
+ Invalid zip installer file
+ Please check if there is plugin.json in {0}
+
Plugins Manager
Management of installing, uninstalling or updating Flow Launcher plugins
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 25182f6d3d2..6ccd781c3b3 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -242,6 +242,18 @@ internal async ValueTask> RequestUpdateAsync(string search, Cancell
if (FilesFolders.IsZipFilePath(search, checkFileExists: true))
{
pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search);
+
+ if (pluginFromLocalPath == null) return new List
+ {
+ new()
+ {
+ Title = Context.API.GetTranslation("plugin_pluginsmanager_invalid_zip_title"),
+ SubTitle = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_invalid_zip_subtitle"),
+ search),
+ IcoPath = icoPath
+ }
+ };
+
pluginFromLocalPath.LocalInstallPath = search;
updateFromLocalPath = true;
}
@@ -559,6 +571,20 @@ internal List InstallFromLocalPath(string localPath)
{
var plugin = Utilities.GetPluginInfoFromZip(localPath);
+ if (plugin == null)
+ {
+ return new List
+ {
+ new()
+ {
+ Title = Context.API.GetTranslation("plugin_pluginsmanager_invalid_zip_title"),
+ SubTitle = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_invalid_zip_subtitle"),
+ localPath),
+ IcoPath = icoPath
+ }
+ };
+ }
+
plugin.LocalInstallPath = localPath;
return new List
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
index 4bb78f6ff8d..d76ce40c41e 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
@@ -65,9 +65,7 @@ internal static UserPlugin GetPluginInfoFromZip(string filePath)
using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath))
{
- var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString();
- ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath);
-
+ var pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json");
if (pluginJsonEntry != null)
{
using Stream stream = pluginJsonEntry.Open();
From a3a0c59fa3e2c3372a7c08f51d522a4a6e49149e Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 16:29:45 +0800
Subject: [PATCH 14/36] Check url nullability
---
Flow.Launcher/Helper/PluginInstallationHelper.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher/Helper/PluginInstallationHelper.cs
index 0d3d2df672c..0e94566b8c9 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -259,6 +259,9 @@ await App.API.ShowProgressBoxAsync(prgBoxTitle,
private static bool InstallSourceKnown(string url)
{
+ if (string.IsNullOrEmpty(url))
+ return false;
+
var pieces = url.Split('/');
if (pieces.Length < 4)
From bdb3616977529f256d312486b23c54687603a3f7 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 16:34:18 +0800
Subject: [PATCH 15/36] Improve string resource
---
Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index bb0d6e5fb39..742d5d8b936 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -47,7 +47,7 @@
Plugin {0} has already been modified. Please restart Flow before making any further changes.
Invalid zip installer file
- Please check if there is plugin.json in {0}
+ Please check if there is a plugin.json in {0}
Plugins Manager
From 8e6a410cfcd6ebe56b893797bc4ba646e73c8ba7 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 17:06:29 +0800
Subject: [PATCH 16/36] Use url host
---
Flow.Launcher/Helper/PluginInstallationHelper.cs | 13 ++++++++-----
.../PluginsManager.cs | 13 ++++++++-----
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher/Helper/PluginInstallationHelper.cs
index 0e94566b8c9..ea5f07bda15 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -268,13 +268,16 @@ private static bool InstallSourceKnown(string url)
return false;
var author = pieces[3];
+ var acceptedHost = "github.com";
var acceptedSource = "https://github.com";
var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
- return url.StartsWith(acceptedSource) &&
- App.API.GetAllPlugins().Any(x =>
- !string.IsNullOrEmpty(x.Metadata.Website) &&
- x.Metadata.Website.StartsWith(constructedUrlPart)
- );
+ if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost)
+ return false;
+
+ return App.API.GetAllPlugins().Any(x =>
+ !string.IsNullOrEmpty(x.Metadata.Website) &&
+ x.Metadata.Website.StartsWith(constructedUrlPart)
+ );
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 6ccd781c3b3..c7c3ff3a27b 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -626,14 +626,17 @@ private bool InstallSourceKnown(string url)
return false;
var author = pieces[3];
+ var acceptedHost = "github.com";
var acceptedSource = "https://github.com";
var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
- return url.StartsWith(acceptedSource) &&
- Context.API.GetAllPlugins().Any(x =>
- !string.IsNullOrEmpty(x.Metadata.Website) &&
- x.Metadata.Website.StartsWith(constructedUrlPart)
- );
+ if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost)
+ return false;
+
+ return Context.API.GetAllPlugins().Any(x =>
+ !string.IsNullOrEmpty(x.Metadata.Website) &&
+ x.Metadata.Website.StartsWith(constructedUrlPart)
+ );
}
internal async ValueTask> RequestInstallOrUpdateAsync(string search, CancellationToken token,
From 19cb3eaf6a52f651986301b06de0246ed6e9ee90 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 18:20:47 +0800
Subject: [PATCH 17/36] Fix typos
---
Flow.Launcher/Helper/PluginInstallationHelper.cs | 2 +-
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher/Helper/PluginInstallationHelper.cs
index ea5f07bda15..ea8195e5726 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -235,7 +235,7 @@ await App.API.ShowProgressBoxAsync(prgBoxTitle,
{
if (reportProgress == null)
{
- // when reportProgress is null, it means there is expcetion with the progress box
+ // when reportProgress is null, it means there is exception with the progress box
// so we record it with exceptionHappened and return so that progress box will close instantly
exceptionHappened = true;
return;
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index c7c3ff3a27b..c1d3a81a2a1 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -209,7 +209,7 @@ await Context.API.ShowProgressBoxAsync(prgBoxTitle,
{
if (reportProgress == null)
{
- // when reportProgress is null, it means there is expcetion with the progress box
+ // when reportProgress is null, it means there is exception with the progress box
// so we record it with exceptionHappened and return so that progress box will close instantly
exceptionHappened = true;
return;
From 9e868e7e3fec02ac340b79cc1f9d505d33616176 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 29 Jun 2025 22:35:14 +0800
Subject: [PATCH 18/36] Move plugin installer location
---
.../Plugin/PluginInstaller.cs | 100 +++++++++---------
.../SettingsPanePluginStoreViewModel.cs | 4 +-
.../ViewModel/PluginStoreItemViewModel.cs | 6 +-
Flow.Launcher/ViewModel/PluginViewModel.cs | 3 +-
.../ViewModel/SelectBrowserViewModel.cs | 1 -
5 files changed, 58 insertions(+), 56 deletions(-)
rename Flow.Launcher/Helper/PluginInstallationHelper.cs => Flow.Launcher.Core/Plugin/PluginInstaller.cs (71%)
diff --git a/Flow.Launcher/Helper/PluginInstallationHelper.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
similarity index 71%
rename from Flow.Launcher/Helper/PluginInstallationHelper.cs
rename to Flow.Launcher.Core/Plugin/PluginInstaller.cs
index ea8195e5726..a69ab322e85 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -10,24 +10,28 @@
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
-namespace Flow.Launcher.Helper;
+namespace Flow.Launcher.Core.Plugin;
///
/// Helper class for installing, updating, and uninstalling plugins.
///
-public static class PluginInstallationHelper
+public static class PluginInstaller
{
- private static readonly string ClassName = nameof(PluginInstallationHelper);
+ private static readonly string ClassName = nameof(PluginInstaller);
private static readonly Settings Settings = Ioc.Default.GetRequiredService();
+ // 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 static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{
- if (App.API.ShowMsgBox(
+ if (API.ShowMsgBox(
string.Format(
- App.API.GetTranslation("InstallPromptSubtitle"),
+ API.GetTranslation("InstallPromptSubtitle"),
newPlugin.Name, newPlugin.Author, Environment.NewLine),
- App.API.GetTranslation("InstallPromptTitle"),
+ API.GetTranslation("InstallPromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try
@@ -44,7 +48,7 @@ public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
if (!newPlugin.IsFromLocalInstallPath)
{
await DownloadFileAsync(
- $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts);
}
else
@@ -63,7 +67,7 @@ await DownloadFileAsync(
throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
}
- App.API.InstallPlugin(newPlugin, filePath);
+ API.InstallPlugin(newPlugin, filePath);
if (!newPlugin.IsFromLocalInstallPath)
{
@@ -72,21 +76,21 @@ await DownloadFileAsync(
}
catch (Exception e)
{
- App.API.LogException(ClassName, "Failed to install plugin", e);
- App.API.ShowMsgError(App.API.GetTranslation("ErrorInstallingPlugin"));
+ API.LogException(ClassName, "Failed to install plugin", e);
+ API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin"));
return; // don’t restart on failure
}
if (Settings.AutoRestartAfterChanging)
{
- App.API.RestartApp();
+ API.RestartApp();
}
else
{
- App.API.ShowMsg(
- App.API.GetTranslation("installbtn"),
+ API.ShowMsg(
+ API.GetTranslation("installbtn"),
string.Format(
- App.API.GetTranslation(
+ API.GetTranslation(
"InstallSuccessNoRestart"),
newPlugin.Name));
}
@@ -108,17 +112,17 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
}
catch (Exception e)
{
- App.API.LogException(ClassName, "Failed to validate zip file", e);
- App.API.ShowMsgError(App.API.GetTranslation("ZipFileNotHavePluginJson"));
+ API.LogException(ClassName, "Failed to validate zip file", e);
+ API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson"));
return;
}
if (Settings.ShowUnknownSourceWarning)
{
if (!InstallSourceKnown(plugin.Website)
- && App.API.ShowMsgBox(string.Format(
- App.API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine),
- App.API.GetTranslation("InstallFromUnknownSourceTitle"),
+ && API.ShowMsgBox(string.Format(
+ API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine),
+ API.GetTranslation("InstallFromUnknownSourceTitle"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
return;
}
@@ -128,39 +132,39 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
{
- if (App.API.ShowMsgBox(
+ if (API.ShowMsgBox(
string.Format(
- App.API.GetTranslation("UninstallPromptSubtitle"),
+ API.GetTranslation("UninstallPromptSubtitle"),
oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
- App.API.GetTranslation("UninstallPromptTitle"),
+ API.GetTranslation("UninstallPromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
- var removePluginSettings = App.API.ShowMsgBox(
- App.API.GetTranslation("KeepPluginSettingsSubtitle"),
- App.API.GetTranslation("KeepPluginSettingsTitle"),
+ var removePluginSettings = API.ShowMsgBox(
+ API.GetTranslation("KeepPluginSettingsSubtitle"),
+ API.GetTranslation("KeepPluginSettingsTitle"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
try
{
- await App.API.UninstallPluginAsync(oldPlugin, removePluginSettings);
+ await API.UninstallPluginAsync(oldPlugin, removePluginSettings);
}
catch (Exception e)
{
- App.API.LogException(ClassName, "Failed to uninstall plugin", e);
- App.API.ShowMsgError(App.API.GetTranslation("ErrorUninstallingPlugin"));
+ API.LogException(ClassName, "Failed to uninstall plugin", e);
+ API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin"));
return; // don’t restart on failure
}
if (Settings.AutoRestartAfterChanging)
{
- App.API.RestartApp();
+ API.RestartApp();
}
else
{
- App.API.ShowMsg(
- App.API.GetTranslation("uninstallbtn"),
+ API.ShowMsg(
+ API.GetTranslation("uninstallbtn"),
string.Format(
- App.API.GetTranslation(
+ API.GetTranslation(
"UninstallSuccessNoRestart"),
oldPlugin.Name));
}
@@ -168,11 +172,11 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
{
- if (App.API.ShowMsgBox(
+ if (API.ShowMsgBox(
string.Format(
- App.API.GetTranslation("UpdatePromptSubtitle"),
+ API.GetTranslation("UpdatePromptSubtitle"),
oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
- App.API.GetTranslation("UpdatePromptTitle"),
+ API.GetTranslation("UpdatePromptTitle"),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try
@@ -184,7 +188,7 @@ public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin,
if (!newPlugin.IsFromLocalInstallPath)
{
await DownloadFileAsync(
- $"{App.API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
+ $"{API.GetTranslation("DownloadingPlugin")} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts);
}
else
@@ -198,25 +202,25 @@ await DownloadFileAsync(
return;
}
- await App.API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
+ await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
}
catch (Exception e)
{
- App.API.LogException(ClassName, "Failed to update plugin", e);
- App.API.ShowMsgError(App.API.GetTranslation("ErrorUpdatingPlugin"));
+ API.LogException(ClassName, "Failed to update plugin", e);
+ API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
return; // don’t restart on failure
}
if (Settings.AutoRestartAfterChanging)
{
- App.API.RestartApp();
+ API.RestartApp();
}
else
{
- App.API.ShowMsg(
- App.API.GetTranslation("updatebtn"),
+ API.ShowMsg(
+ API.GetTranslation("updatebtn"),
string.Format(
- App.API.GetTranslation(
+ API.GetTranslation(
"UpdateSuccessNoRestart"),
newPlugin.Name));
}
@@ -230,7 +234,7 @@ private static async Task DownloadFileAsync(string prgBoxTitle, string downloadU
if (showProgress)
{
var exceptionHappened = false;
- await App.API.ShowProgressBoxAsync(prgBoxTitle,
+ await API.ShowProgressBoxAsync(prgBoxTitle,
async (reportProgress) =>
{
if (reportProgress == null)
@@ -242,18 +246,18 @@ await App.API.ShowProgressBoxAsync(prgBoxTitle,
}
else
{
- await App.API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
+ await API.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
}
}, cts.Cancel);
// if exception happened while downloading and user does not cancel downloading,
// we need to redownload the plugin
if (exceptionHappened && (!cts.IsCancellationRequested))
- await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
}
else
{
- await App.API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
+ await API.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
}
}
@@ -275,7 +279,7 @@ private static bool InstallSourceKnown(string url)
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost)
return false;
- return App.API.GetAllPlugins().Any(x =>
+ return API.GetAllPlugins().Any(x =>
!string.IsNullOrEmpty(x.Metadata.Website) &&
x.Metadata.Website.StartsWith(constructedUrlPart)
);
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index 2b12ba70fb4..efe67d01664 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -3,7 +3,7 @@
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
-using Flow.Launcher.Helper;
+using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@@ -106,7 +106,7 @@ private async Task InstallPluginAsync()
$"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip");
if (!string.IsNullOrEmpty(file))
- await PluginInstallationHelper.InstallPluginAndCheckRestartAsync(file);
+ await PluginInstaller.InstallPluginAndCheckRestartAsync(file);
}
private static string GetFileFromDialog(string title, string filter = "")
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index f03d2740e33..a985ca7ff03 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -66,13 +66,13 @@ private async Task ShowCommandQueryAsync(string action)
switch (action)
{
case "install":
- await PluginInstallationHelper.InstallPluginAndCheckRestartAsync(_newPlugin);
+ await PluginInstaller.InstallPluginAndCheckRestartAsync(_newPlugin);
break;
case "uninstall":
- await PluginInstallationHelper.UninstallPluginAndCheckRestartAsync(_oldPluginPair.Metadata);
+ await PluginInstaller.UninstallPluginAndCheckRestartAsync(_oldPluginPair.Metadata);
break;
case "update":
- await PluginInstallationHelper.UpdatePluginAndCheckRestartAsync(_newPlugin, _oldPluginPair.Metadata);
+ await PluginInstaller.UpdatePluginAndCheckRestartAsync(_newPlugin, _oldPluginPair.Metadata);
break;
default:
break;
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index 131972e8577..ea222d02374 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -5,7 +5,6 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
@@ -173,7 +172,7 @@ private void OpenSourceCodeLink()
[RelayCommand]
private async Task OpenDeletePluginWindowAsync()
{
- await PluginInstallationHelper.UninstallPluginAndCheckRestartAsync(PluginPair.Metadata);
+ await PluginInstaller.UninstallPluginAndCheckRestartAsync(PluginPair.Metadata);
}
[RelayCommand]
diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
index 1eee6dba5d3..67bbbd9301f 100644
--- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
@@ -1,6 +1,5 @@
using System.Collections.ObjectModel;
using System.Linq;
-using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
From dafb0caac4a4a7745e26c5e49e1a94933e1089c0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 30 Jun 2025 12:42:09 +0800
Subject: [PATCH 19/36] Remove unused using
---
Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index a985ca7ff03..f5523212e13 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -2,7 +2,6 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Helper;
using Flow.Launcher.Plugin;
using Version = SemanticVersioning.Version;
From ea25a661eec75486ce7b8ad3dad41addac97b2fe Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 30 Jun 2025 12:46:41 +0800
Subject: [PATCH 20/36] Fix an issue that after uninstalling pm, store no
longer fetches plugin until clicking on refresh.
---
Flow.Launcher/App.xaml.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 5df1f88aeba..6d1499f2dc7 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -208,6 +208,9 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
Http.Proxy = _settings.Proxy;
+ // Initialize plugin manifest before initializing plugins so that they can use the manifest instantly
+ await API.UpdatePluginManifestAsync();
+
await PluginManager.InitializePluginsAsync();
// Change language after all plugins are initialized because we need to update plugin title based on their api
From 5b8b84a34c97b2503732311e3fd9b00a5b89b2a0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 30 Jun 2025 12:58:44 +0800
Subject: [PATCH 21/36] Fix code comments
---
Flow.Launcher.Core/Plugin/PluginInstaller.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index a69ab322e85..781ad3ff085 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -78,7 +78,7 @@ await DownloadFileAsync(
{
API.LogException(ClassName, "Failed to install plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorInstallingPlugin"));
- return; // don’t restart on failure
+ return; // do not restart on failure
}
if (Settings.AutoRestartAfterChanging)
@@ -152,7 +152,7 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
{
API.LogException(ClassName, "Failed to uninstall plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUninstallingPlugin"));
- return; // don’t restart on failure
+ return; // don not restart on failure
}
if (Settings.AutoRestartAfterChanging)
@@ -208,7 +208,7 @@ await DownloadFileAsync(
{
API.LogException(ClassName, "Failed to update plugin", e);
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
- return; // don’t restart on failure
+ return; // do not restart on failure
}
if (Settings.AutoRestartAfterChanging)
From 01e749ac88e2aa4f9bc5e5bcc8ee5920ec68e9ee Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 30 Jun 2025 13:07:19 +0800
Subject: [PATCH 22/36] Fix an issue that store install/uninstall same plugin
without restart shows error message error, should say already
installed/uninstalled
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 24 +++++++++++++++-------
Flow.Launcher/Languages/en.xaml | 5 +++++
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 9b525f331d2..5b74d80f025 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -542,7 +542,8 @@ public static bool PluginModified(string id)
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
- InstallPlugin(newVersion, zipFilePath, checkModified:false);
+ var success = InstallPlugin(newVersion, zipFilePath, checkModified:false);
+ if (!success) return;
await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
_modifiedPlugins.Add(existingVersion.ID);
}
@@ -561,12 +562,13 @@ public static async Task UninstallPluginAsync(PluginMetadata plugin, bool remove
#region Internal functions
- internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
+ internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
{
if (checkModified && PluginModified(plugin.ID))
{
- // Distinguish exception from installing same or less version
- throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
+ API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.GetTranslation("pluginModifiedAlreadyMessage"));
+ return false;
}
// Unzip plugin files to temp folder
@@ -584,12 +586,16 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
{
- throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist");
+ API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath));
+ return false;
}
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
{
- throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
+ API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.GetTranslation("pluginExistAlreadyMessage"));
+ return false;
}
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
@@ -633,13 +639,17 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
{
_modifiedPlugins.Add(plugin.ID);
}
+
+ return true;
}
internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
{
if (checkModified && PluginModified(plugin.ID))
{
- throw new ArgumentException($"Plugin {plugin.Name} has been modified");
+ API.ShowMsg(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), plugin.Name),
+ API.GetTranslation("pluginModifiedAlreadyMessage"));
+ return;
}
if (removePluginSettings || removePluginFromSettings)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index e71ece19d90..1cc03d6b13a 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -175,6 +175,11 @@
Plugins: {0} - Fail to remove plugin settings files, please remove them manually
Fail to remove plugin cache
Plugins: {0} - Fail to remove plugin cache files, please remove them manually
+ Fail to install {0}
+ Fail to uninstall {0}
+ This plugin has been installed or uninstalled already, please restart Flow
+ Unable to find plugin.json from the extracted zip file, or this path {0} does not exist
+ A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin
Plugin Store
From 6318bbe1878d54ebc2c19670e6a9d06e1035b95b Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 30 Jun 2025 13:16:59 +0800
Subject: [PATCH 23/36] Fix an issue that pm install/uninstall same plugin
without restart says correct message but another message also pops up to say
it's successfully installed
---
Flow.Launcher.Core/Plugin/PluginInstaller.cs | 21 ++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index 781ad3ff085..4cdab09f90a 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -27,6 +27,13 @@ public static class PluginInstaller
public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{
+ if (API.PluginModified(newPlugin.ID))
+ {
+ API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), newPlugin.Name),
+ API.GetTranslation("pluginModifiedAlreadyMessage"));
+ return;
+ }
+
if (API.ShowMsgBox(
string.Format(
API.GetTranslation("InstallPromptSubtitle"),
@@ -117,6 +124,13 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
return;
}
+ if (API.PluginModified(plugin.ID))
+ {
+ API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.GetTranslation("pluginModifiedAlreadyMessage"));
+ return;
+ }
+
if (Settings.ShowUnknownSourceWarning)
{
if (!InstallSourceKnown(plugin.Website)
@@ -132,6 +146,13 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
{
+ if (API.PluginModified(oldPlugin.ID))
+ {
+ API.ShowMsg(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), oldPlugin.Name),
+ API.GetTranslation("pluginModifiedAlreadyMessage"));
+ return;
+ }
+
if (API.ShowMsgBox(
string.Format(
API.GetTranslation("UninstallPromptSubtitle"),
From 1bb72869200565d955b8cc690242bae526ebe71c Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 08:57:32 +0800
Subject: [PATCH 24/36] Show error message instead of message
---
Flow.Launcher.Core/Plugin/PluginInstaller.cs | 6 +++---
Flow.Launcher.Core/Plugin/PluginManager.cs | 10 ++++++----
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index 4cdab09f90a..e39c24430e1 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -29,7 +29,7 @@ public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{
if (API.PluginModified(newPlugin.ID))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), newPlugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), newPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
@@ -126,7 +126,7 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
if (API.PluginModified(plugin.ID))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
@@ -148,7 +148,7 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
{
if (API.PluginModified(oldPlugin.ID))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), oldPlugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), oldPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 5b74d80f025..5bff71007c5 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -566,7 +566,7 @@ internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
{
if (checkModified && PluginModified(plugin.ID))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return false;
}
@@ -586,14 +586,14 @@ internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
string.Format(API.GetTranslation("fileNotFoundMessage"), pluginFolderPath));
return false;
}
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
API.GetTranslation("pluginExistAlreadyMessage"));
return false;
}
@@ -647,7 +647,7 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
{
if (checkModified && PluginModified(plugin.ID))
{
- API.ShowMsg(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
@@ -712,6 +712,8 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
{
_modifiedPlugins.Add(plugin.ID);
}
+
+ return;
}
#endregion
From a8bc55dcb54270b9946e3a80517e21bf2fff4670 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 08:59:32 +0800
Subject: [PATCH 25/36] Add return for UninstallPluginAsync
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 5bff71007c5..97a1987a9d6 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -542,8 +542,8 @@ public static bool PluginModified(string id)
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
- var success = InstallPlugin(newVersion, zipFilePath, checkModified:false);
- if (!success) return;
+ var installSuccess = InstallPlugin(newVersion, zipFilePath, checkModified:false);
+ if (!installSuccess) return;
await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
_modifiedPlugins.Add(existingVersion.ID);
}
@@ -643,13 +643,13 @@ internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
return true;
}
- internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
+ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
{
if (checkModified && PluginModified(plugin.ID))
{
API.ShowMsgError(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
- return;
+ return false;
}
if (removePluginSettings || removePluginFromSettings)
@@ -713,7 +713,7 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
_modifiedPlugins.Add(plugin.ID);
}
- return;
+ return true;
}
#endregion
From ce6c2cb1b2053071c240cd689c0d9d93a8dd8ee1 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 09:05:21 +0800
Subject: [PATCH 26/36] Add return value for api functions
---
Flow.Launcher.Core/Plugin/PluginInstaller.cs | 15 +++++++++++---
Flow.Launcher.Core/Plugin/PluginManager.cs | 16 ++++++++-------
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 17 +++++++++++-----
Flow.Launcher/PublicAPIInstance.cs | 6 +++---
.../PluginsManager.cs | 20 +++++++++++++------
5 files changed, 50 insertions(+), 24 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index e39c24430e1..e69dd58abfa 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -74,7 +74,10 @@ await DownloadFileAsync(
throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
}
- API.InstallPlugin(newPlugin, filePath);
+ if (!API.InstallPlugin(newPlugin, filePath))
+ {
+ return;
+ }
if (!newPlugin.IsFromLocalInstallPath)
{
@@ -167,7 +170,10 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
try
{
- await API.UninstallPluginAsync(oldPlugin, removePluginSettings);
+ if (!await API.UninstallPluginAsync(oldPlugin, removePluginSettings))
+ {
+ return;
+ }
}
catch (Exception e)
{
@@ -223,7 +229,10 @@ await DownloadFileAsync(
return;
}
- await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath);
+ if (!await API.UpdatePluginAsync(oldPlugin, newPlugin, filePath))
+ {
+ return;
+ }
}
catch (Exception e)
{
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 97a1987a9d6..db02486f101 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -540,22 +540,24 @@ public static bool PluginModified(string id)
return _modifiedPlugins.Contains(id);
}
- public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
+ public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
var installSuccess = InstallPlugin(newVersion, zipFilePath, checkModified:false);
- if (!installSuccess) return;
- await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
+ if (!installSuccess) return false;
+ var uninstallSuccess = await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
+ if (!uninstallSuccess) return false;
_modifiedPlugins.Add(existingVersion.ID);
+ return true;
}
- public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
+ public static bool InstallPlugin(UserPlugin plugin, string zipFilePath)
{
- InstallPlugin(plugin, zipFilePath, checkModified: true);
+ return InstallPlugin(plugin, zipFilePath, checkModified: true);
}
- public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
+ public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
{
- await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
+ return await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
}
#endregion
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index f47ee5e11c8..dfa7c9e97e1 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -547,8 +547,10 @@ public interface IPublicAPI
///
/// 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);
+ ///
+ /// True if the plugin is updated successfully, false otherwise.
+ ///
+ public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath);
///
/// Install a plugin. By default will remove the zip file if installation is from url,
@@ -558,7 +560,10 @@ public interface IPublicAPI
///
/// 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);
+ ///
+ /// True if the plugin is installed successfully, false otherwise.
+ ///
+ public bool InstallPlugin(UserPlugin plugin, string zipFilePath);
///
/// Uninstall a plugin
@@ -567,8 +572,10 @@ public interface IPublicAPI
///
/// 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);
+ ///
+ /// True if the plugin is updated successfully, false otherwise.
+ ///
+ public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);
///
/// Log debug message of the time taken to execute a method
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 6e82032ffe2..43952ffba8e 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -566,13 +566,13 @@ public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, Canc
public bool PluginModified(string id) => PluginManager.PluginModified(id);
- public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) =>
+ public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) =>
PluginManager.UpdatePluginAsync(pluginMetadata, plugin, zipFilePath);
- public void InstallPlugin(UserPlugin plugin, string zipFilePath) =>
+ public bool InstallPlugin(UserPlugin plugin, string zipFilePath) =>
PluginManager.InstallPlugin(plugin, zipFilePath);
- public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) =>
+ public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) =>
PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings);
public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = "") =>
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index c1d3a81a2a1..9eded239dc2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -352,8 +352,11 @@ await DownloadFileAsync(
}
else
{
- await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
- downloadToFilePath);
+ if (!await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
+ downloadToFilePath))
+ {
+ return;
+ }
if (Settings.AutoRestartAfterChanging)
{
@@ -456,8 +459,9 @@ await DownloadFileAsync(
if (cts.IsCancellationRequested)
return;
else
- await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
- downloadToFilePath);
+ if (!await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
+ downloadToFilePath))
+ return;
}
catch (Exception ex)
{
@@ -686,7 +690,8 @@ private void Install(UserPlugin plugin, string downloadedFilePath)
try
{
- Context.API.InstallPlugin(plugin, downloadedFilePath);
+ if (!Context.API.InstallPlugin(plugin, downloadedFilePath))
+ return;
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
@@ -779,7 +784,10 @@ private async Task UninstallAsync(PluginMetadata plugin)
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
- await Context.API.UninstallPluginAsync(plugin, removePluginSettings);
+ if (!await Context.API.UninstallPluginAsync(plugin, removePluginSettings))
+ {
+ return;
+ }
}
catch (ArgumentException e)
{
From 71043be0782cf81050b0dc2f55bcce53ede71115 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 12:00:43 +0800
Subject: [PATCH 27/36] Fix string format issue
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 9eded239dc2..3d3abba4a0d 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -793,7 +793,7 @@ private async Task UninstallAsync(PluginMetadata plugin)
{
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"));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
}
}
}
From 07947c5e1eb7c0e1ee064b4e3fac0a8a9ef91d2e Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 12:34:48 +0800
Subject: [PATCH 28/36] Fix an issue that pm install/uninstall same plugin
without restart says correct message but another message also pops up to say
it's successfully installed. It should only one pop up notification to say
that the plugin already installed/uninstalled
---
.../PluginsManager.cs | 54 +++++++++++++++----
1 file changed, 45 insertions(+), 9 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 3d3abba4a0d..3d74e26f2e6 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -114,6 +114,14 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin)
return;
}
+ if (Context.API.PluginModified(plugin.ID))
+ {
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
+ plugin.Name));
+ return;
+ }
+
string message;
if (Settings.AutoRestartAfterChanging)
{
@@ -158,7 +166,8 @@ await DownloadFileAsync(
if (cts.IsCancellationRequested)
return;
else
- Install(plugin, filePath);
+ if (!Install(plugin, filePath))
+ return;
}
catch (HttpRequestException e)
{
@@ -273,6 +282,7 @@ where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version
select
new
{
+ existingPlugin.Metadata.ID,
pluginUpdateSource.Name,
pluginUpdateSource.Author,
CurrentVersion = existingPlugin.Metadata.Version,
@@ -302,6 +312,14 @@ where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version
IcoPath = x.IcoPath,
Action = e =>
{
+ if (Context.API.PluginModified(x.ID))
+ {
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
+ x.Name));
+ return false;
+ }
+
string message;
if (Settings.AutoRestartAfterChanging)
{
@@ -421,6 +439,14 @@ await DownloadFileAsync(
IcoPath = icoPath,
AsyncAction = async e =>
{
+ if (resultsForUpdate.All(x => Context.API.PluginModified(x.ID)))
+ {
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
+ string.Join(" ", resultsForUpdate.Select(x => x.Name))));
+ return false;
+ }
+
string message;
if (Settings.AutoRestartAfterChanging)
{
@@ -442,6 +468,7 @@ await DownloadFileAsync(
return false;
}
+ var anyPluginSuccess = false;
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(),
@@ -462,6 +489,8 @@ await DownloadFileAsync(
if (!await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
downloadToFilePath))
return;
+
+ anyPluginSuccess = true;
}
catch (Exception ex)
{
@@ -474,6 +503,8 @@ await DownloadFileAsync(
}
}));
+ if (!anyPluginSuccess) return false;
+
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
@@ -682,7 +713,7 @@ internal async ValueTask> RequestInstallOrUpdateAsync(string search
return Search(results, search);
}
- private void Install(UserPlugin plugin, string downloadedFilePath)
+ private bool Install(UserPlugin plugin, string downloadedFilePath)
{
if (!File.Exists(downloadedFilePath))
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}",
@@ -691,10 +722,12 @@ private void Install(UserPlugin plugin, string downloadedFilePath)
try
{
if (!Context.API.InstallPlugin(plugin, downloadedFilePath))
- return;
+ return false;
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
+
+ return true;
}
catch (FileNotFoundException e)
{
@@ -716,6 +749,8 @@ private void Install(UserPlugin plugin, string downloadedFilePath)
plugin.Name));
Context.API.LogException(ClassName, e.Message, e);
}
+
+ return false;
}
internal List RequestUninstall(string search)
@@ -751,7 +786,10 @@ internal List RequestUninstall(string search)
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
Context.API.HideMainWindow();
- await UninstallAsync(x.Metadata);
+ if (!await UninstallAsync(x.Metadata))
+ {
+ return false;
+ }
if (Settings.AutoRestartAfterChanging)
{
Context.API.RestartApp();
@@ -776,7 +814,7 @@ internal List RequestUninstall(string search)
return Search(results, search);
}
- private async Task UninstallAsync(PluginMetadata plugin)
+ private async Task UninstallAsync(PluginMetadata plugin)
{
try
{
@@ -784,16 +822,14 @@ private async Task UninstallAsync(PluginMetadata plugin)
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
- if (!await Context.API.UninstallPluginAsync(plugin, removePluginSettings))
- {
- return;
- }
+ return await Context.API.UninstallPluginAsync(plugin, removePluginSettings);
}
catch (ArgumentException e)
{
Context.API.LogException(ClassName, e.Message, e);
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
+ return false;
}
}
}
From 5bfdc583b12422c572934039eed60cadb0355df1 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 13:18:26 +0800
Subject: [PATCH 29/36] Change the notification message title about plugin
already installed/uninstalled from 'Fail to install ' to ' is already
installed'. Do the same for uninstalling notification title.
---
Flow.Launcher.Core/Plugin/PluginInstaller.cs | 6 +++---
Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++--
Flow.Launcher/Languages/en.xaml | 3 ++-
.../Languages/en.xaml | 2 ++
.../PluginsManager.cs | 20 +++++++++++++------
5 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index e69dd58abfa..6e2ff99ff94 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -29,7 +29,7 @@ public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{
if (API.PluginModified(newPlugin.ID))
{
- API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), newPlugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), newPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
@@ -129,7 +129,7 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
if (API.PluginModified(plugin.ID))
{
- API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
@@ -151,7 +151,7 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
{
if (API.PluginModified(oldPlugin.ID))
{
- API.ShowMsgError(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), oldPlugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), oldPlugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return;
}
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 4dae01e61b3..979d77df9c2 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -568,7 +568,7 @@ internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
{
if (checkModified && PluginModified(plugin.ID))
{
- API.ShowMsgError(string.Format(API.GetTranslation("failedToInstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return false;
}
@@ -649,7 +649,7 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, boo
{
if (checkModified && PluginModified(plugin.ID))
{
- API.ShowMsgError(string.Format(API.GetTranslation("failedToUninstallPluginTitle"), plugin.Name),
+ API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), plugin.Name),
API.GetTranslation("pluginModifiedAlreadyMessage"));
return false;
}
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 1cc03d6b13a..c9ddfb91c9f 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -175,9 +175,10 @@
Plugins: {0} - Fail to remove plugin settings files, please remove them manually
Fail to remove plugin cache
Plugins: {0} - Fail to remove plugin cache files, please remove them manually
+ Plugin {0} is installed or uninstalled already
+ Please restart Flow before making any further changes
Fail to install {0}
Fail to uninstall {0}
- This plugin has been installed or uninstalled already, please restart Flow
Unable to find plugin.json from the extracted zip file, or this path {0} does not exist
A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 742d5d8b936..ff2158f79a7 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -45,6 +45,8 @@
Plugin {0} successfully updated. Please restart Flow.
{0} plugins successfully updated. Please restart Flow.
Plugin {0} has already been modified. Please restart Flow before making any further changes.
+ Plugin {0} has already been modified
+ Please restart Flow before making any further changes
Invalid zip installer file
Please check if there is a plugin.json in {0}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 3d74e26f2e6..76d5eeafd4b 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -116,9 +116,9 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin)
if (Context.API.PluginModified(plugin.ID))
{
- Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
- plugin.Name));
+ Context.API.ShowMsgError(
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error_title"), plugin.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error_message"));
return;
}
@@ -314,9 +314,9 @@ where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version
{
if (Context.API.PluginModified(x.ID))
{
- Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"),
- x.Name));
+ Context.API.ShowMsgError(
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error_title"), x.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error_message"));
return false;
}
@@ -765,6 +765,14 @@ internal List RequestUninstall(string search)
IcoPath = x.Metadata.IcoPath,
AsyncAction = async e =>
{
+ if (Context.API.PluginModified(x.Metadata.ID))
+ {
+ Context.API.ShowMsgError(
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error_title"), x.Metadata.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error_message"));
+ return false;
+ }
+
string message;
if (Settings.AutoRestartAfterChanging)
{
From 650a15673662f3f1311017116197a31582684d5e Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 1 Jul 2025 13:21:48 +0800
Subject: [PATCH 30/36] Improve strings
---
Flow.Launcher/Languages/en.xaml | 2 +-
Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index c9ddfb91c9f..6396e9a0820 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -175,7 +175,7 @@
Plugins: {0} - Fail to remove plugin settings files, please remove them manually
Fail to remove plugin cache
Plugins: {0} - Fail to remove plugin cache files, please remove them manually
- Plugin {0} is installed or uninstalled already
+ Plugin {0} is modified already
Please restart Flow before making any further changes
Fail to install {0}
Fail to uninstall {0}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index ff2158f79a7..5048fa0dbf2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -45,7 +45,7 @@
Plugin {0} successfully updated. Please restart Flow.
{0} plugins successfully updated. Please restart Flow.
Plugin {0} has already been modified. Please restart Flow before making any further changes.
- Plugin {0} has already been modified
+ Plugin {0} is modified already
Please restart Flow before making any further changes
Invalid zip installer file
From ae206c35a57b5364dd0021e34d775d947c27e257 Mon Sep 17 00:00:00 2001
From: Jeremy
Date: Thu, 3 Jul 2025 21:53:39 +1000
Subject: [PATCH 31/36] update Plugin Store & Plugins Manager texts
---
Flow.Launcher/Languages/en.xaml | 6 +++---
.../Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 6 +++---
Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 6396e9a0820..fadda08bc05 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -133,8 +133,8 @@
This can only be edited if plugin supports Home feature and Home Page is enabled.
Show Search Window at Foremost
Overrides other programs' 'Always on Top' setting and displays Flow in the foremost position.
- Automatically restart after changing plugins
- Automatically restart Flow Launcher after installing/uninstalling/updating plugins
+ Restart after modifying plugin via Plugin Store
+ Restart Flow Launcher automatically after installing/uninstalling/updating plugin via Plugin Store
Show unknown source warning
Show warning when installing plugins from unknown sources
@@ -175,7 +175,7 @@
Plugins: {0} - Fail to remove plugin settings files, please remove them manually
Fail to remove plugin cache
Plugins: {0} - Fail to remove plugin cache files, please remove them manually
- Plugin {0} is modified already
+ {0} modified already
Please restart Flow before making any further changes
Fail to install {0}
Fail to uninstall {0}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 5048fa0dbf2..fa2e65240ae 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -45,7 +45,7 @@
Plugin {0} successfully updated. Please restart Flow.
{0} plugins successfully updated. Please restart Flow.
Plugin {0} has already been modified. Please restart Flow before making any further changes.
- Plugin {0} is modified already
+ {0} modified already
Please restart Flow before making any further changes
Invalid zip installer file
@@ -53,7 +53,7 @@
Plugins Manager
- Management of installing, uninstalling or updating Flow Launcher plugins
+ Install, uninstall or update Flow Launcher plugins via the search window
Unknown Author
@@ -68,5 +68,5 @@
Install from unknown source warning
- Automatically restart Flow Launcher after installing/uninstalling/updating plugins
+ Restart Flow Launcher automatically after installing/uninstalling/updating plugin via Plugins Manager
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
index 327011ac31d..949e9e9db8b 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
@@ -4,7 +4,7 @@
"pm"
],
"Name": "Plugins Manager",
- "Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
+ "Description": "Install, uninstall or update Flow Launcher plugins via the search window",
"Author": "Jeremy Wu",
"Version": "1.0.0",
"Language": "csharp",
From c06e6d1a6b6b5df09dc3d8437a8b35ffd6c606c1 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 3 Jul 2025 20:53:15 +0800
Subject: [PATCH 32/36] Check modified state when updating plugins
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index c6e14dcb9f3..a3e451b62d7 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -549,10 +549,19 @@ public static bool PluginModified(string id)
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
+ if (PluginModified(existingVersion.ID))
+ {
+ API.ShowMsgError(string.Format(API.GetTranslation("pluginModifiedAlreadyTitle"), existingVersion.Name),
+ API.GetTranslation("pluginModifiedAlreadyMessage"));
+ return false;
+ }
+
var installSuccess = InstallPlugin(newVersion, zipFilePath, checkModified:false);
if (!installSuccess) return false;
+
var uninstallSuccess = await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
if (!uninstallSuccess) return false;
+
_modifiedPlugins.Add(existingVersion.ID);
return true;
}
From 052bbb9d75891c4e9c7907deb3d5704c800aa1a8 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 6 Jul 2025 16:49:17 +0800
Subject: [PATCH 33/36] Fix format
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index a3e451b62d7..1d9425232db 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -556,10 +556,10 @@ public static async Task UpdatePluginAsync(PluginMetadata existingVersion,
return false;
}
- var installSuccess = InstallPlugin(newVersion, zipFilePath, checkModified:false);
+ var installSuccess = InstallPlugin(newVersion, zipFilePath, checkModified: false);
if (!installSuccess) return false;
- var uninstallSuccess = await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
+ var uninstallSuccess = await UninstallPluginAsync(existingVersion, removePluginFromSettings: false, removePluginSettings: false, checkModified: false);
if (!uninstallSuccess) return false;
_modifiedPlugins.Add(existingVersion.ID);
From edb145091dd57fcf9d5394a51096ff46a6b972a3 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 6 Jul 2025 20:12:26 +0800
Subject: [PATCH 34/36] Fix build issue
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index e2098a8aa6b..3860787f9b1 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -576,7 +576,7 @@ public static async Task UpdatePluginAsync(PluginMetadata existingVersion,
var uninstallSuccess = await UninstallPluginAsync(existingVersion, removePluginFromSettings: false, removePluginSettings: false, checkModified: false);
if (!uninstallSuccess) return false;
- _modifiedPlugins.Add(existingVersion.ID);
+ ModifiedPlugins.Add(existingVersion.ID);
return true;
}
From a63c8b036bd4c29dd0355b39ccadeda5381f7880 Mon Sep 17 00:00:00 2001
From: Jeremy
Date: Mon, 7 Jul 2025 21:35:12 +1000
Subject: [PATCH 35/36] updates to method summary and correct spell check
errors
---
.github/actions/spelling/expect.txt | 2 +
.github/actions/spelling/patterns.txt | 1 +
Flow.Launcher.Core/Plugin/PluginInstaller.cs | 42 +++++++++++++++++--
Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +-
Flow.Launcher/ProgressBoxEx.xaml.cs | 20 ++++-----
.../PluginsManager.cs | 4 +-
6 files changed, 55 insertions(+), 16 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 177c00fa2e4..6bbd57ac616 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -99,3 +99,5 @@ pluginsmanager
alreadyexists
Softpedia
img
+Reloadable
+metadatas
diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt
index 5ef8859fc27..f308ec5993a 100644
--- a/.github/actions/spelling/patterns.txt
+++ b/.github/actions/spelling/patterns.txt
@@ -133,3 +133,4 @@
\bPortuguês (Brasil)\b
\bčeština\b
\bPortuguês\b
+\bIoc\b
diff --git a/Flow.Launcher.Core/Plugin/PluginInstaller.cs b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
index 6e2ff99ff94..33963c01a5b 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -13,7 +13,7 @@
namespace Flow.Launcher.Core.Plugin;
///
-/// Helper class for installing, updating, and uninstalling plugins.
+/// Class for installing, updating, and uninstalling plugins.
///
public static class PluginInstaller
{
@@ -25,6 +25,11 @@ public static class PluginInstaller
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+ ///
+ /// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed.
+ ///
+ /// The plugin to install.
+ /// A Task representing the asynchronous install operation.
public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{
if (API.PluginModified(newPlugin.ID))
@@ -106,6 +111,11 @@ await DownloadFileAsync(
}
}
+ ///
+ /// Installs a plugin from a local zip file and restarts the application if required by settings. Validates the zip and prompts user for confirmation.
+ ///
+ /// The path to the plugin zip file.
+ /// A Task representing the asynchronous install operation.
public static async Task InstallPluginAndCheckRestartAsync(string filePath)
{
UserPlugin plugin;
@@ -147,6 +157,11 @@ public static async Task InstallPluginAndCheckRestartAsync(string filePath)
await InstallPluginAndCheckRestartAsync(plugin);
}
+ ///
+ /// Uninstalls a plugin and restarts the application if required by settings. Prompts user for confirmation and whether to keep plugin settings.
+ ///
+ /// The plugin metadata to uninstall.
+ /// A Task representing the asynchronous uninstall operation.
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
{
if (API.PluginModified(oldPlugin.ID))
@@ -197,6 +212,12 @@ public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldP
}
}
+ ///
+ /// Updates a plugin to a new version and restarts the application if required by settings. Prompts user for confirmation and handles download if needed.
+ ///
+ /// The new plugin version to install.
+ /// The existing plugin metadata to update.
+ /// A Task representing the asynchronous update operation.
public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
{
if (API.ShowMsgBox(
@@ -256,7 +277,17 @@ await DownloadFileAsync(
}
}
- private static async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
+ ///
+ /// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
+ ///
+ /// The title for the progress box.
+ /// The URL to download from.
+ /// The local file path to save to.
+ /// Cancellation token source for cancelling the download.
+ /// Whether to delete the file if it already exists.
+ /// Whether to show a progress box during download.
+ /// A Task representing the asynchronous download operation.
+ private static async Task DownloadFileAsync(string progressBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
{
if (deleteFile && File.Exists(filePath))
File.Delete(filePath);
@@ -264,7 +295,7 @@ private static async Task DownloadFileAsync(string prgBoxTitle, string downloadU
if (showProgress)
{
var exceptionHappened = false;
- await API.ShowProgressBoxAsync(prgBoxTitle,
+ await API.ShowProgressBoxAsync(progressBoxTitle,
async (reportProgress) =>
{
if (reportProgress == null)
@@ -291,6 +322,11 @@ await API.ShowProgressBoxAsync(prgBoxTitle,
}
}
+ ///
+ /// Determines if the plugin install source is a known/approved source (e.g., GitHub and matches an existing plugin author).
+ ///
+ /// The URL to check.
+ /// True if the source is known, otherwise false.
private static bool InstallSourceKnown(string url)
{
if (string.IsNullOrEmpty(url))
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 3860787f9b1..60790abedcd 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -18,7 +18,7 @@
namespace Flow.Launcher.Core.Plugin
{
///
- /// The entry for managing Flow Launcher plugins
+ /// Class for co-ordinating and managing all plugin lifecycle.
///
public static class PluginManager
{
diff --git a/Flow.Launcher/ProgressBoxEx.xaml.cs b/Flow.Launcher/ProgressBoxEx.xaml.cs
index 840c8bade87..11946334869 100644
--- a/Flow.Launcher/ProgressBoxEx.xaml.cs
+++ b/Flow.Launcher/ProgressBoxEx.xaml.cs
@@ -19,32 +19,32 @@ private ProgressBoxEx(Action cancelProgress)
public static async Task ShowAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null)
{
- ProgressBoxEx prgBox = null;
+ ProgressBoxEx progressBox = null;
try
{
if (!Application.Current.Dispatcher.CheckAccess())
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
- prgBox = new ProgressBoxEx(cancelProgress)
+ progressBox = new ProgressBoxEx(cancelProgress)
{
Title = caption
};
- prgBox.TitleTextBlock.Text = caption;
- prgBox.Show();
+ progressBox.TitleTextBlock.Text = caption;
+ progressBox.Show();
});
}
else
{
- prgBox = new ProgressBoxEx(cancelProgress)
+ progressBox = new ProgressBoxEx(cancelProgress)
{
Title = caption
};
- prgBox.TitleTextBlock.Text = caption;
- prgBox.Show();
+ progressBox.TitleTextBlock.Text = caption;
+ progressBox.Show();
}
- await reportProgressAsync(prgBox.ReportProgress).ConfigureAwait(false);
+ await reportProgressAsync(progressBox.ReportProgress).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -58,12 +58,12 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
- prgBox?.Close();
+ progressBox?.Close();
});
}
else
{
- prgBox?.Close();
+ progressBox?.Close();
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 76d5eeafd4b..efbe8d7ba7d 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -205,7 +205,7 @@ await DownloadFileAsync(
}
}
- private async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
+ private async Task DownloadFileAsync(string progressBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
{
if (deleteFile && File.Exists(filePath))
File.Delete(filePath);
@@ -213,7 +213,7 @@ private async Task DownloadFileAsync(string prgBoxTitle, string downloadUrl, str
if (showProgress)
{
var exceptionHappened = false;
- await Context.API.ShowProgressBoxAsync(prgBoxTitle,
+ await Context.API.ShowProgressBoxAsync(progressBoxTitle,
async (reportProgress) =>
{
if (reportProgress == null)
From 236bff1c109c359905a1d416da04a5a00d6840df Mon Sep 17 00:00:00 2001
From: Jeremy
Date: Mon, 7 Jul 2025 21:57:03 +1000
Subject: [PATCH 36/36] fix spelling
---
.github/actions/spelling/expect.txt | 2 ++
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 26 +++++++++----------
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 6bbd57ac616..5b3419041a6 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -101,3 +101,5 @@ Softpedia
img
Reloadable
metadatas
+WMP
+VSTHRD
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 1c59aebe3af..cfa813d3f2b 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -23,8 +23,8 @@ public interface IPublicAPI
///
/// query text
///
- /// Force requery. By default, Flow Launcher will not fire query if your query is same with existing one.
- /// Set this to to force Flow Launcher requerying
+ /// Force requery. By default, Flow Launcher will not fire query if your query is same with existing one.
+ /// Set this to to force Flow Launcher re-querying
///
void ChangeQuery(string query, bool requery = false);
@@ -49,7 +49,7 @@ public interface IPublicAPI
///
/// Text to save on clipboard
/// When true it will directly copy the file/folder from the path specified in text
- /// Whether to show the default notification from this method after copy is done.
+ /// Whether to show the default notification from this method after copy is done.
/// It will show file/folder/text is copied successfully.
/// Turn this off to show your own notification after copy is done.>
public void CopyToClipboard(string text, bool directCopy = false, bool showDefaultNotification = true);
@@ -65,7 +65,7 @@ public interface IPublicAPI
void SavePluginSettings();
///
- /// Reloads any Plugins that have the
+ /// Reloads any Plugins that have the
/// IReloadable implemented. It refeshes
/// Plugin's in memory data with new content
/// added by user.
@@ -97,7 +97,7 @@ public interface IPublicAPI
/// Show the MainWindow when hiding
///
void ShowMainWindow();
-
+
///
/// Focus the query text box in the main window
///
@@ -115,7 +115,7 @@ public interface IPublicAPI
bool IsMainWindowVisible();
///
- /// Invoked when the visibility of the main window has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
+ /// Invoked when the visibility of the main window has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
///
event VisibilityChangedEventHandler VisibilityChanged;
@@ -171,7 +171,7 @@ public interface IPublicAPI
string GetTranslation(string key);
///
- /// Get all loaded plugins
+ /// Get all loaded plugins
///
///
List GetAllPlugins();
@@ -229,7 +229,7 @@ public interface IPublicAPI
MatchResult FuzzySearch(string query, string stringToCompare);
///
- /// Http download the spefic url and return as string
+ /// Http download the specific url and return as string
///
/// URL to call Http Get
/// Cancellation Token
@@ -237,7 +237,7 @@ public interface IPublicAPI
Task HttpGetStringAsync(string url, CancellationToken token = default);
///
- /// Http download the spefic url and return as stream
+ /// Http download the specific url and return as stream
///
/// URL to call Http Get
/// Cancellation Token
@@ -305,8 +305,8 @@ public interface IPublicAPI
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
+ /// 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
///
void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "");
@@ -393,7 +393,7 @@ public interface IPublicAPI
///
/// Reloads the query.
- /// When current results are from context menu or history, it will go back to query results before requerying.
+ /// When current results are from context menu or history, it will go back to query results before re-querying.
///
/// Choose the first result after reload if true; keep the last selected result if false. Default is true.
public void ReQuery(bool reselect = true);
@@ -610,7 +610,7 @@ public interface IPublicAPI
bool IsApplicationDarkTheme();
///
- /// Invoked when the actual theme of the application has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
+ /// Invoked when the actual theme of the application has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
///
event ActualApplicationThemeChangedEventHandler ActualApplicationThemeChanged;
}