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/38] 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 ce1269a29..024e727ce 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -176,6 +176,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
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 22ab2016c..24f74e15d 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 c0c5613de..452e026d7 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 => _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 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 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 @@ namespace Flow.Launcher.ViewModel
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 @@ namespace Flow.Launcher.ViewModel
}
[RelayCommand]
- private void ShowCommandQuery(string action)
+ private async Task ShowCommandQueryAsync(string action)
{
- var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty;
- App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}");
- App.API.ShowMainWindow();
+ 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)
+ {
+ 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 01fa3d203..bda05a02d 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 @@ namespace Flow.Launcher.ViewModel
}
}
- 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 @@ namespace Flow.Launcher.ViewModel
}
[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/38] 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 24f74e15d..2166bdd8c 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/38] 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 3e823d635..a69c0dbd7 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -82,6 +82,8 @@ namespace Flow.Launcher.ViewModel
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/38] 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 a69c0dbd7..6b2cf6eed 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -142,6 +142,7 @@ namespace Flow.Launcher.ViewModel
{
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 @@ namespace Flow.Launcher.ViewModel
{
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 @@ namespace Flow.Launcher.ViewModel
{
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/38] 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 aae8dd764..5b14ad0b7 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -6,6 +6,7 @@ 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;
@@ -24,6 +25,8 @@ namespace Flow.Launcher.Core.Plugin
{
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 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
}
}
+ 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 6b2cf6eed..a504b7a05 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 @@ namespace Flow.Launcher.ViewModel
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 bda05a02d..f902fb037 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -172,7 +172,7 @@ namespace Flow.Launcher.ViewModel
[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/38] 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 0b2b042d4..9f8e51047 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -191,6 +191,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
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 7f00926f1..2b42b8f84 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 452e026d7..1966c4c0d 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/38] 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 ef831e940..f7a2461ab 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 @@ namespace Flow.Launcher.Core.Plugin
}
}
+ 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 @@ namespace Flow.Launcher.Core.Plugin
}
}
+ 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 b3fdd6892..6ce5d17d0 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 07df0682d..b9b7c12fa 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 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
}
}
+ [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 9312b0c2d..68f78d46c 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/38] 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 d831774fb..ac27c3b40 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/38] 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 f7a2461ab..9b525f331 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 @@ namespace Flow.Launcher.Core.Plugin
{
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 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
}
}
- 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 000000000..570c5d34c
--- /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 b9b7c12fa..bce7201b8 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -4,7 +4,7 @@ using System.Linq;
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 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
$"{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 a504b7a05..f03d2740e 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 @@ namespace Flow.Launcher.ViewModel
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 f902fb037..131972e85 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -5,6 +5,7 @@ using System.Windows.Media;
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 @@ namespace Flow.Launcher.ViewModel
[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/38] 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 570c5d34c..d7ce2934c 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -57,19 +57,17 @@ public static class PluginInstallationHelper
{
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 @@ public static class PluginInstallationHelper
{
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/38] 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 bce7201b8..2b12ba70f 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 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
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 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
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/38] 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 d7ce2934c..0d3d2df67 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -98,9 +98,7 @@ public static class PluginInstallationHelper
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/38] 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 573ca9051..bb0d6e5fb 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 25182f6d3..6ccd781c3 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -242,6 +242,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
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 4bb78f6ff..d76ce40c4 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs
@@ -65,9 +65,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
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/38] 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 0d3d2df67..0e94566b8 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -259,6 +259,9 @@ public static class PluginInstallationHelper
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/38] 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 bb0d6e5fb..742d5d8b9 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/38] 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 0e94566b8..ea5f07bda 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -268,13 +268,16 @@ public static class PluginInstallationHelper
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 6ccd781c3..c7c3ff3a2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -626,14 +626,17 @@ namespace Flow.Launcher.Plugin.PluginsManager
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/38] 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 ea5f07bda..ea8195e57 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher/Helper/PluginInstallationHelper.cs
@@ -235,7 +235,7 @@ public static class PluginInstallationHelper
{
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 c7c3ff3a2..c1d3a81a2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -209,7 +209,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
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/38] 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 ea8195e57..a69ab322e 100644
--- a/Flow.Launcher/Helper/PluginInstallationHelper.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -10,24 +10,28 @@ using CommunityToolkit.Mvvm.DependencyInjection;
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 class PluginInstallationHelper
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 @@ public static class PluginInstallationHelper
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 @@ public static class PluginInstallationHelper
}
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 class PluginInstallationHelper
}
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 class PluginInstallationHelper
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 class PluginInstallationHelper
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 class PluginInstallationHelper
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 @@ public static class PluginInstallationHelper
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 @@ public static class PluginInstallationHelper
if (showProgress)
{
var exceptionHappened = false;
- await App.API.ShowProgressBoxAsync(prgBoxTitle,
+ await API.ShowProgressBoxAsync(prgBoxTitle,
async (reportProgress) =>
{
if (reportProgress == null)
@@ -242,18 +246,18 @@ public static class PluginInstallationHelper
}
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 @@ public static class PluginInstallationHelper
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 2b12ba70f..efe67d016 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
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 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
$"{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 f03d2740e..a985ca7ff 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -66,13 +66,13 @@ namespace Flow.Launcher.ViewModel
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 131972e85..ea222d023 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -5,7 +5,6 @@ using System.Windows.Media;
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 @@ namespace Flow.Launcher.ViewModel
[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 1eee6dba5..67bbbd930 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/38] 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 a985ca7ff..f5523212e 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/38] 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 5df1f88ae..6d1499f2d 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -208,6 +208,9 @@ namespace Flow.Launcher
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/38] 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 a69ab322e..781ad3ff0 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -78,7 +78,7 @@ public static class PluginInstaller
{
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 class PluginInstaller
{
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 @@ public static class PluginInstaller
{
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/38] 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 9b525f331..5b74d80f0 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -542,7 +542,8 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
#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 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
{
_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 e71ece19d..1cc03d6b1 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/38] 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 781ad3ff0..4cdab09f9 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 class PluginInstaller
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 class PluginInstaller
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/38] 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 4cdab09f9..e39c24430 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -29,7 +29,7 @@ public static class PluginInstaller
{
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 class PluginInstaller
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 class PluginInstaller
{
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 5b74d80f0..5bff71007 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -566,7 +566,7 @@ namespace Flow.Launcher.Core.Plugin
{
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 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
{
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 @@ namespace Flow.Launcher.Core.Plugin
{
_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/38] 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 5bff71007..97a1987a9 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -542,8 +542,8 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
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 @@ namespace Flow.Launcher.Core.Plugin
_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/38] 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 e39c24430..e69dd58ab 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -74,7 +74,10 @@ public static class PluginInstaller
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 class PluginInstaller
try
{
- await API.UninstallPluginAsync(oldPlugin, removePluginSettings);
+ if (!await API.UninstallPluginAsync(oldPlugin, removePluginSettings))
+ {
+ return;
+ }
}
catch (Exception e)
{
@@ -223,7 +229,10 @@ public static class PluginInstaller
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 97a1987a9..db02486f1 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -540,22 +540,24 @@ namespace Flow.Launcher.Core.Plugin
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 f47ee5e11..dfa7c9e97 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -547,8 +547,10 @@ namespace Flow.Launcher.Plugin
///
/// 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 @@ namespace Flow.Launcher.Plugin
///
/// 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 @@ namespace Flow.Launcher.Plugin
///
/// 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 6e82032ff..43952ffba 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -566,13 +566,13 @@ namespace Flow.Launcher
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 c1d3a81a2..9eded239d 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -352,8 +352,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
- Context.API.InstallPlugin(plugin, downloadedFilePath);
+ if (!Context.API.InstallPlugin(plugin, downloadedFilePath))
+ return;
if (!plugin.IsFromLocalInstallPath)
File.Delete(downloadedFilePath);
@@ -779,7 +784,10 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
- await 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/38] 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 9eded239d..3d3abba4a 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -793,7 +793,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
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/38] 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 3d3abba4a..3d74e26f2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -114,6 +114,14 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (cts.IsCancellationRequested)
return;
else
- Install(plugin, filePath);
+ if (!Install(plugin, filePath))
+ return;
}
catch (HttpRequestException e)
{
@@ -273,6 +282,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
select
new
{
+ existingPlugin.Metadata.ID,
pluginUpdateSource.Name,
pluginUpdateSource.Author,
CurrentVersion = existingPlugin.Metadata.Version,
@@ -302,6 +312,14 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
return false;
}
+ var anyPluginSuccess = false;
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(),
@@ -462,6 +489,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
if (!await Context.API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin,
downloadToFilePath))
return;
+
+ anyPluginSuccess = true;
}
catch (Exception ex)
{
@@ -474,6 +503,8 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
}));
+ if (!anyPluginSuccess) return false;
+
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
@@ -682,7 +713,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
plugin.Name));
Context.API.LogException(ClassName, e.Message, e);
}
+
+ return false;
}
internal List RequestUninstall(string search)
@@ -751,7 +786,10 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, search);
}
- private async Task UninstallAsync(PluginMetadata plugin)
+ private async Task UninstallAsync(PluginMetadata plugin)
{
try
{
@@ -784,16 +822,14 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
- 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/38] 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 e69dd58ab..6e2ff99ff 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -29,7 +29,7 @@ public static class PluginInstaller
{
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 class PluginInstaller
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 class PluginInstaller
{
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 4dae01e61..979d77df9 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -568,7 +568,7 @@ namespace Flow.Launcher.Core.Plugin
{
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 @@ namespace Flow.Launcher.Core.Plugin
{
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 1cc03d6b1..c9ddfb91c 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 742d5d8b9..ff2158f79 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 3d74e26f2..76d5eeafd 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -116,9 +116,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
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 @@ namespace Flow.Launcher.Plugin.PluginsManager
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/38] 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 c9ddfb91c..6396e9a08 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 ff2158f79..5048fa0db 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/38] 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 6396e9a08..fadda08bc 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 5048fa0db..fa2e65240 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 327011ac3..949e9e9db 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/38] 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 c6e14dcb9..a3e451b62 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -549,10 +549,19 @@ namespace Flow.Launcher.Core.Plugin
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/38] 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 a3e451b62..1d9425232 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -556,10 +556,10 @@ namespace Flow.Launcher.Core.Plugin
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/38] 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 e2098a8aa..3860787f9 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -576,7 +576,7 @@ namespace Flow.Launcher.Core.Plugin
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 770eec529658c720f35d19359ba739a1df6f9047 Mon Sep 17 00:00:00 2001
From: Peter de Vroom
Date: Mon, 7 Jul 2025 13:50:07 +1000
Subject: [PATCH 35/38] Use System.Version to compare versions
Add error checking
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 444a44a0a..225529062 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -550,8 +550,12 @@ namespace Flow.Launcher.Core.Plugin
private static bool SameOrLesserPluginVersionExists(string metadataPath)
{
var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath));
+ if (!Version.TryParse(newMetadata.Version, out var newVersion))
+ throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
+
return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
- && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
+ && Version.TryParse(x.Metadata.Version, out var version)
+ && newVersion <= version);
}
#region Public functions
From 988a3bc09e670183fd5af18e9f89f50cd60e2bfc Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 7 Jul 2025 14:00:07 +0800
Subject: [PATCH 36/38] Do not throw exception & Fix blank lines
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 225529062..03ebfc31c 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -550,8 +550,9 @@ namespace Flow.Launcher.Core.Plugin
private static bool SameOrLesserPluginVersionExists(string metadataPath)
{
var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath));
+
if (!Version.TryParse(newMetadata.Version, out var newVersion))
- throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
+ return true; // If version is not valid, we assume it is lesser than any existing version
return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
&& Version.TryParse(x.Metadata.Version, out var version)
From a63c8b036bd4c29dd0355b39ccadeda5381f7880 Mon Sep 17 00:00:00 2001
From: Jeremy
Date: Mon, 7 Jul 2025 21:35:12 +1000
Subject: [PATCH 37/38] 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 177c00fa2..6bbd57ac6 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 5ef8859fc..f308ec599 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 6e2ff99ff..33963c01a 100644
--- a/Flow.Launcher.Core/Plugin/PluginInstaller.cs
+++ b/Flow.Launcher.Core/Plugin/PluginInstaller.cs
@@ -13,7 +13,7 @@ using Flow.Launcher.Plugin;
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 @@ public static class PluginInstaller
}
}
+ ///
+ /// 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 class PluginInstaller
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 class PluginInstaller
}
}
+ ///
+ /// 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 @@ public static class PluginInstaller
}
}
- 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 @@ public static class PluginInstaller
if (showProgress)
{
var exceptionHappened = false;
- await API.ShowProgressBoxAsync(prgBoxTitle,
+ await API.ShowProgressBoxAsync(progressBoxTitle,
async (reportProgress) =>
{
if (reportProgress == null)
@@ -291,6 +322,11 @@ public static class PluginInstaller
}
}
+ ///
+ /// 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 3860787f9..60790abed 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -18,7 +18,7 @@ using ISavable = Flow.Launcher.Plugin.ISavable;
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 840c8bade..119463348 100644
--- a/Flow.Launcher/ProgressBoxEx.xaml.cs
+++ b/Flow.Launcher/ProgressBoxEx.xaml.cs
@@ -19,32 +19,32 @@ namespace Flow.Launcher
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 @@ namespace Flow.Launcher
{
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 76d5eeafd..efbe8d7ba 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -205,7 +205,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
}
- 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 @@ namespace Flow.Launcher.Plugin.PluginsManager
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 38/38] 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 6bbd57ac6..5b3419041 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 1c59aebe3..cfa813d3f 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -23,8 +23,8 @@ namespace Flow.Launcher.Plugin
///
/// 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 @@ namespace Flow.Launcher.Plugin
///
/// 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 @@ namespace Flow.Launcher.Plugin
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 @@ namespace Flow.Launcher.Plugin
/// Show the MainWindow when hiding
///
void ShowMainWindow();
-
+
///
/// Focus the query text box in the main window
///
@@ -115,7 +115,7 @@ namespace Flow.Launcher.Plugin
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 @@ namespace Flow.Launcher.Plugin
string GetTranslation(string key);
///
- /// Get all loaded plugins
+ /// Get all loaded plugins
///
///
List GetAllPlugins();
@@ -229,7 +229,7 @@ namespace Flow.Launcher.Plugin
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 @@ namespace Flow.Launcher.Plugin
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 @@ namespace Flow.Launcher.Plugin
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 @@ namespace Flow.Launcher.Plugin
///
/// 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 @@ namespace Flow.Launcher.Plugin
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;
}