From 31131ea4a0379dfd8177c3605f1fab9460542928 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Thu, 28 Sep 2023 23:45:11 +0800
Subject: [PATCH 01/43] Remove download success notification
---
.../Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 7 -------
1 file changed, 7 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 683904ea0..6b6945d37 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -136,9 +136,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), plugin.Name));
-
Install(plugin, filePath);
}
catch (Exception e)
@@ -223,10 +220,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), x.Name));
-
Install(x.PluginNewUserPlugin, downloadToFilePath);
Context.API.RestartApp();
From 1334798c6d420dc1a0676835b5b41779924dc5f6 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Fri, 29 Sep 2023 13:06:42 +0800
Subject: [PATCH 02/43] Add AutoRestartAfterChanging option
- Option and UI
- New prompts and notification messages
---
.../Languages/en.xaml | 11 ++-
.../PluginsManager.cs | 96 +++++++++++++++----
.../Settings.cs | 2 +
.../ViewModels/SettingsViewModel.cs | 6 ++
.../Views/PluginsManagerSettings.xaml | 12 +++
5 files changed, 107 insertions(+), 20 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 1f74a49a2..d08087ac2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -8,7 +8,9 @@
Successfully downloaded {0}
Error: Unable to download the plugin
{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.
+ {0} by {1} {2}{2}Would you like to uninstall this plugin?
{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.
+ {0} by {1} {2}{2}Would you like to install this plugin?
Plugin Install
Installing Plugin
Download and install {0}
@@ -21,15 +23,19 @@
No update available
All plugins are up to date
{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.
+ {0} by {1} {2}{2}Would you like to update this plugin?
Plugin Update
This plugin has an update, would you like to see it?
This plugin is already installed
Plugin Manifest Download Failed
Please check if you can connect to github.com. This error means you may not be able to install or update plugins.
+ Plugin {0} successfully updated. Restarting Flow, please wait...
Installing from an unknown source
You are installing this plugin 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 via settings)
-
-
+
+ Plugin {0} successfully installed. Please manually restart Flow.
+ Plugin {0} successfully uninstalled. Please manually restart Flow.
+ Plugin {0} successfully updated. Please manually restart Flow.
Plugins Manager
@@ -48,4 +54,5 @@
Install from unknown source warning
+ Automatically restart Flow Launcher after installing/uninstalling/updating plugins
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 6b6945d37..77fa3b981 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -117,9 +117,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
return;
}
- var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
- plugin.Name, plugin.Author,
- Environment.NewLine, Environment.NewLine);
+ string message;
+ if (Settings.AutoRestartAfterChanging)
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"),
+ plugin.Name, plugin.Author,
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt_no_restart"),
+ plugin.Name, plugin.Author,
+ Environment.NewLine);
+ }
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
MessageBoxButton.YesNo) == MessageBoxResult.No)
@@ -140,6 +150,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}
catch (Exception e)
{
+ // TODO use toast to optimize error prompt
if (e is HttpRequestException)
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"),
Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"));
@@ -153,10 +164,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
return;
}
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), plugin.Name));
-
- Context.API.RestartApp();
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"),
+ plugin.Name));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"),
+ plugin.Name));
+ }
}
internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false)
@@ -201,10 +221,20 @@ namespace Flow.Launcher.Plugin.PluginsManager
IcoPath = x.IcoPath,
Action = e =>
{
- string message = string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
- x.Name, x.Author,
- Environment.NewLine, Environment.NewLine);
+
+ string message;
+ if (Settings.AutoRestartAfterChanging)
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"),
+ x.Name, x.Author,
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"),
+ x.Name, x.Author,
+ Environment.NewLine);
+ }
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
@@ -215,14 +245,26 @@ namespace Flow.Launcher.Plugin.PluginsManager
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
$"{x.Name}-{x.NewVersion}.zip");
- Task.Run(async delegate
+ _ = Task.Run(async delegate
{
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
Install(x.PluginNewUserPlugin, downloadToFilePath);
- Context.API.RestartApp();
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_restart"),
+ x.Name));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_update_success_no_restart"),
+ x.Name));
+ }
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
@@ -454,10 +496,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
IcoPath = x.Metadata.IcoPath,
Action = e =>
{
- string message = string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
- x.Metadata.Name, x.Metadata.Author,
- Environment.NewLine, Environment.NewLine);
+ string message;
+ if (Settings.AutoRestartAfterChanging)
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"),
+ x.Metadata.Name, x.Metadata.Author,
+ Environment.NewLine, Environment.NewLine);
+ }
+ else
+ {
+ message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"),
+ x.Metadata.Name, x.Metadata.Author,
+ Environment.NewLine);
+ }
if (MessageBox.Show(message,
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
@@ -465,7 +516,16 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Application.Current.MainWindow.Hide();
Uninstall(x.Metadata);
- Context.API.RestartApp();
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_success_no_restart"),
+ x.Metadata.Name));
+ }
return true;
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
index aa35f02b5..f23ff71f0 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
@@ -9,5 +9,7 @@
internal const string UpdateCommand = "update";
public bool WarnFromUnknownSource { get; set; } = true;
+
+ public bool AutoRestartAfterChanging { get; set; } = false;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
index 672884f80..1c71507fc 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs
@@ -17,5 +17,11 @@
get => Settings.WarnFromUnknownSource;
set => Settings.WarnFromUnknownSource = value;
}
+
+ public bool AutoRestartAfterChanging
+ {
+ get => Settings.AutoRestartAfterChanging;
+ set => Settings.AutoRestartAfterChanging = value;
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
index a62a032f7..18be0d2ca 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml
@@ -12,10 +12,22 @@
+
+
+
+
+
From 0c8729f7fb5edfed1dd2641c6d205013b81753e7 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Tue, 26 Sep 2023 20:16:20 +0800
Subject: [PATCH 03/43] Fix wrong doc
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 474ad6f0a..e6d9126c6 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -107,7 +107,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Message icon path (relative path to your plugin folder)
+ /// Full path to icon
void ShowMsg(string title, string subTitle = "", string iconPath = "");
///
@@ -115,7 +115,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Message icon path (relative path to your plugin folder)
+ /// Full path to icon
/// when true will use main windows as the owner
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
From cc2ae7c768516db1d113b0088b75aa105f863feb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 30 Oct 2023 23:09:43 +0000
Subject: [PATCH 04/43] Bump Microsoft.Data.Sqlite from 7.0.10 to 7.0.13
Bumps [Microsoft.Data.Sqlite](https://github.com/dotnet/efcore) from 7.0.10 to 7.0.13.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.10...v7.0.13)
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 9701c2ee8..edaa3dd29 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -1,4 +1,4 @@
-
+
Library
@@ -56,7 +56,7 @@
-
+
\ No newline at end of file
From 983a4c56871d6b4d0b78563d8816574981abea9a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 7 Nov 2023 21:22:47 +0000
Subject: [PATCH 05/43] Bump FSharp.Core from 7.0.400 to 7.0.401
Bumps [FSharp.Core](https://github.com/dotnet/fsharp) from 7.0.400 to 7.0.401.
- [Release notes](https://github.com/dotnet/fsharp/releases)
- [Changelog](https://github.com/dotnet/fsharp/blob/main/release-notes.md)
- [Commits](https://github.com/dotnet/fsharp/commits)
---
updated-dependencies:
- dependency-name: FSharp.Core
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher.Core/Flow.Launcher.Core.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 42f233dad..312dfdd9e 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -54,7 +54,7 @@
-
+
From 72134e8f9a9ae08b1af3935cc6d345d29aa3c445 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 8 Nov 2023 21:51:48 +0800
Subject: [PATCH 06/43] Tweak notification text
Co-authored-by: Jeremy Wu
---
.../Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index d08087ac2..1daa73261 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -33,9 +33,9 @@
Installing from an unknown source
You are installing this plugin 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 via settings)
- Plugin {0} successfully installed. Please manually restart Flow.
- Plugin {0} successfully uninstalled. Please manually restart Flow.
- Plugin {0} successfully updated. Please manually restart Flow.
+ Plugin {0} successfully installed. Please restart Flow.
+ Plugin {0} successfully uninstalled. Please restart Flow.
+ Plugin {0} successfully updated. Please restart Flow.
Plugins Manager
From 0870c5a783ce6bb73e696085d2b15a8b425925a7 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 8 Nov 2023 22:20:19 +0800
Subject: [PATCH 07/43] Revert "Fix wrong doc"
This reverts commit 0c8729f7fb5edfed1dd2641c6d205013b81753e7.
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index e6d9126c6..474ad6f0a 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -107,7 +107,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Full path to icon
+ /// Message icon path (relative path to your plugin folder)
void ShowMsg(string title, string subTitle = "", string iconPath = "");
///
@@ -115,7 +115,7 @@ namespace Flow.Launcher.Plugin
///
/// Message title
/// Message subtitle
- /// Full path to icon
+ /// Message icon path (relative path to your plugin folder)
/// when true will use main windows as the owner
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
From 08da4e34df6ada1c5559c9f4169ac6271877798b Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:06:02 +0800
Subject: [PATCH 08/43] Use toast to improve consistency
---
.../PluginsManager.cs | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 77fa3b981..5d18cf18f 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -148,19 +148,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
Install(plugin, filePath);
}
+ catch (HttpRequestException e)
+ {
+ Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
+ Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
+ return;
+ }
catch (Exception e)
{
- // TODO use toast to optimize error prompt
- if (e is HttpRequestException)
- MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"),
- Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"));
-
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
plugin.Name));
-
- Log.Exception("PluginsManager", "An error occurred while downloading plugin", e, "InstallOrUpdate");
-
+ Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
From e7ffd573f0b6a9994e0e95d65f107356c7ccfec3 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:40:27 +0800
Subject: [PATCH 09/43] Move Install/Uninstall plugin logic to
Core.PluginManager
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 104 ++++++++++++++++++
.../PluginsManager.cs | 103 +++--------------
2 files changed, 120 insertions(+), 87 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index f8c9a3f17..9010f8a6f 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -11,6 +11,9 @@ using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using ISavable = Flow.Launcher.Plugin.ISavable;
+using Flow.Launcher.Plugin.SharedCommands;
+using Mono.Cecil;
+using System.Text.Json;
namespace Flow.Launcher.Core.Plugin
{
@@ -331,5 +334,106 @@ namespace Flow.Launcher.Core.Plugin
RemoveActionKeyword(id, oldActionKeyword);
}
}
+
+ private static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath)
+ {
+ var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length;
+ var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length;
+
+ // adjust path depending on how the plugin is zipped up
+ // the recommended should be to zip up the folder not the contents
+ if (unzippedFolderCount == 1 && unzippedFilesCount == 0)
+ // folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/
+ return Directory.GetDirectories(unzippedParentFolderPath)[0];
+
+ if (unzippedFilesCount > 1)
+ // content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/
+ return unzippedParentFolderPath;
+
+ return string.Empty;
+ }
+
+ private static bool SameOrLesserPluginVersionExists(string metadataPath)
+ {
+ var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath));
+ return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
+ && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
+ }
+
+ public static void Install(UserPlugin plugin, string downloadedFilePath)
+ {
+ var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
+ var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
+
+ if (Directory.Exists(tempFolderPath))
+ Directory.Delete(tempFolderPath, true);
+
+ Directory.CreateDirectory(tempFolderPath);
+
+ var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
+
+ File.Copy(downloadedFilePath, zipFilePath);
+
+ File.Delete(downloadedFilePath);
+
+ System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
+
+ var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
+
+ var metadataJsonFilePath = string.Empty;
+ if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
+ metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
+
+ 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");
+ }
+
+ 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}");
+ }
+
+ var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
+
+ var defaultPluginIDs = new List
+ {
+ "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
+ "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
+ "572be03c74c642baae319fc283e561a8", // Explorer
+ "6A122269676E40EB86EB543B945932B9", // PluginIndicator
+ "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
+ "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
+ "791FC278BA414111B8D1886DFE447410", // Program
+ "D409510CD0D2481F853690A07E6DC426", // Shell
+ "CEA08895D2544B019B2E9C5009600DF4", // Sys
+ "0308FD86DE0A4DEE8D62B9B535370992", // URL
+ "565B73353DBF4806919830B9202EE3BF", // WebSearch
+ "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
+ };
+
+ // Treat default plugin differently, it needs to be removable along with each flow release
+ var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
+ ? DataLocation.PluginsDirectory
+ : Constant.PreinstalledDirectory;
+
+ var newPluginPath = Path.Combine(installDirectory, folderName);
+
+ FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
+
+ Directory.Delete(pluginFolderPath, true);
+ }
+
+ public static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
+ {
+ if (removeSettings)
+ {
+ Settings.Plugins.Remove(plugin.ID);
+ AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
+ }
+
+ // Marked for deletion. Will be deleted on next start up
+ using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 5d18cf18f..20644a2b2 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -151,19 +150,19 @@ namespace Flow.Launcher.Plugin.PluginsManager
catch (HttpRequestException e)
{
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
+ Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
catch (Exception e)
{
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- plugin.Name));
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ plugin.Name));
Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
return;
}
-
+
if (Settings.AutoRestartAfterChanging)
{
Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"),
@@ -411,77 +410,22 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
if (!File.Exists(downloadedFilePath))
return;
-
- var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
- var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
-
- if (Directory.Exists(tempFolderPath))
- Directory.Delete(tempFolderPath, true);
-
- Directory.CreateDirectory(tempFolderPath);
-
- var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
-
- File.Copy(downloadedFilePath, zipFilePath);
-
- File.Delete(downloadedFilePath);
-
- Utilities.UnZip(zipFilePath, tempFolderPluginPath, true);
-
- var pluginFolderPath = Utilities.GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
-
- var metadataJsonFilePath = string.Empty;
- if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
- metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
-
- if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
+ try
{
+ PluginManager.Install(plugin, downloadedFilePath);
+ }
+ catch(FileNotFoundException e)
+ {
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
-
- throw new FileNotFoundException(
- string.Format("Unable to find plugin.json from the extracted zip file, or this path {0} does not exist", pluginFolderPath));
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
-
- if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
+ catch(InvalidOperationException e)
{
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
-
- throw new InvalidOperationException(
- string.Format("A plugin with the same ID and version already exists, " +
- "or the version is greater than this downloaded plugin {0}",
- plugin.Name));
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
-
- var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
-
- var defaultPluginIDs = new List
- {
- "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
- "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
- "572be03c74c642baae319fc283e561a8", // Explorer
- "6A122269676E40EB86EB543B945932B9", // PluginIndicator
- "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
- "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
- "791FC278BA414111B8D1886DFE447410", // Program
- "D409510CD0D2481F853690A07E6DC426", // Shell
- "CEA08895D2544B019B2E9C5009600DF4", // Sys
- "0308FD86DE0A4DEE8D62B9B535370992", // URL
- "565B73353DBF4806919830B9202EE3BF", // WebSearch
- "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
- };
-
- // Treat default plugin differently, it needs to be removable along with each flow release
- var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
- ? DataLocation.PluginsDirectory
- : Constant.PreinstalledDirectory;
-
- var newPluginPath = Path.Combine(installDirectory, folderName);
-
- FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
-
- Directory.Delete(pluginFolderPath, true);
}
internal List RequestUninstall(string search)
@@ -537,24 +481,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, search);
}
- private void Uninstall(PluginMetadata plugin, bool removedSetting = true)
+ private static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
{
- if (removedSetting)
- {
- PluginManager.Settings.Plugins.Remove(plugin.ID);
- PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
- }
-
- // Marked for deletion. Will be deleted on next start up
- using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
- }
-
- private bool SameOrLesserPluginVersionExists(string metadataPath)
- {
- var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath));
- return Context.API.GetAllPlugins()
- .Any(x => x.Metadata.ID == newMetadata.ID
- && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
+ PluginManager.Uninstall(plugin, removeSettings);
}
}
}
From 5b2220b9dafda45af72a20ee7a12136b23fd3dee Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:47:18 +0800
Subject: [PATCH 10/43] Exclude installed plugins in pm install results
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 20644a2b2..af68cc0a8 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -382,6 +382,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
var results =
PluginsManifest
.UserPlugins
+ .Where(x => !PluginExists(x.ID))
.Select(x =>
new Result
{
From 842451db69bba8b40449773a79a80ddda6418d5d Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 00:48:18 +0800
Subject: [PATCH 11/43] Show plugin icons in pm Install results
---
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 af68cc0a8..7b5c09fa7 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -388,7 +388,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Title = $"{x.Name} by {x.Author}",
SubTitle = x.Description,
- IcoPath = icoPath,
+ IcoPath = x.IcoPath,
Action = e =>
{
if (e.SpecialKeyState.CtrlPressed)
From 41721150f91437dc62e9cc4a13bb852f16ebd9e2 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 01:20:32 +0800
Subject: [PATCH 12/43] Add glyphs for pm context menu
---
Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
index 580954f3c..17e9fe2bc 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
@@ -13,6 +13,10 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context = context;
}
+ private readonly GlyphInfo sourcecodeGlyph = new("/Resources/#Segoe Fluent Icons","\uE943");
+ private readonly GlyphInfo issueGlyph = new("/Resources/#Segoe Fluent Icons", "\ued15");
+ private readonly GlyphInfo manifestGlyph = new("/Resources/#Segoe Fluent Icons", "\uea37");
+
public List LoadContextMenus(Result selectedResult)
{
if(selectedResult.ContextData is not UserPlugin pluginManifestInfo)
@@ -36,6 +40,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"),
IcoPath = "Images\\sourcecode.png",
+ Glyph = sourcecodeGlyph,
Action = _ =>
{
Context.API.OpenUrl(pluginManifestInfo.UrlSourceCode);
@@ -47,6 +52,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"),
IcoPath = "Images\\request.png",
+ Glyph = issueGlyph,
Action = _ =>
{
// standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master
@@ -63,6 +69,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"),
IcoPath = "Images\\manifestsite.png",
+ Glyph = manifestGlyph,
Action = _ =>
{
Context.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest");
From 54e255c504ce62c058e9baa255e95d6b73d64599 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 01:28:34 +0800
Subject: [PATCH 13/43] Fix typo
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 9010f8a6f..6720ee61a 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -12,7 +12,6 @@ using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using ISavable = Flow.Launcher.Plugin.ISavable;
using Flow.Launcher.Plugin.SharedCommands;
-using Mono.Cecil;
using System.Text.Json;
namespace Flow.Launcher.Core.Plugin
From bf598887dd942e2e684e2ff3144b9fc9e944d261 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 01:34:01 +0800
Subject: [PATCH 14/43] Make settings field private
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 6720ee61a..ea8b79aa4 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -29,8 +29,7 @@ namespace Flow.Launcher.Core.Plugin
public static IPublicAPI API { private set; get; }
- // todo happlebao, this should not be public, the indicator function should be embeded
- public static PluginsSettings Settings;
+ private static PluginsSettings Settings;
private static List _metadatas;
///
From b7a78362bf38124ccbc72ec0419f611e5c97b530 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 10:25:45 +0800
Subject: [PATCH 15/43] Throw exception when zip not found
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 7b5c09fa7..97cfb67d1 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -267,7 +267,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
}).ContinueWith(t =>
{
Log.Exception("PluginsManager", $"Update failed for {x.Name}",
- t.Exception.InnerException, "RequestUpdate");
+ t.Exception.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
@@ -410,7 +410,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
private void Install(UserPlugin plugin, string downloadedFilePath)
{
if (!File.Exists(downloadedFilePath))
- return;
+ throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
try
{
PluginManager.Install(plugin, downloadedFilePath);
From 69dad1be6c2bca648b54089f62047e78ecadc5e9 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 15:05:24 +0800
Subject: [PATCH 16/43] Check if plugin has been modified when
installing/updating/uninstalling
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 56 ++++++++++++++++++-
.../Languages/en.xaml | 4 +-
.../PluginsManager.cs | 36 ++++++++----
3 files changed, 81 insertions(+), 15 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index ea8b79aa4..64e097379 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -31,6 +31,7 @@ namespace Flow.Launcher.Core.Plugin
private static PluginsSettings Settings;
private static List _metadatas;
+ private static List _modifiedPlugins = new List();
///
/// Directories that will hold Flow Launcher plugin directory
@@ -358,8 +359,42 @@ namespace Flow.Launcher.Core.Plugin
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
}
- public static void Install(UserPlugin plugin, string downloadedFilePath)
+ #region Public functions
+
+ public static bool PluginModified(string uuid)
{
+ return _modifiedPlugins.Contains(uuid);
+ }
+
+ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string downloadedFilePath)
+ {
+ InstallPlugin(newVersion, downloadedFilePath, checkModified:false);
+ UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
+ _modifiedPlugins.Add(existingVersion.ID);
+ }
+
+ public static void InstallPlugin(UserPlugin plugin, string downloadedFilePath)
+ {
+ InstallPlugin(plugin, downloadedFilePath, true);
+ }
+
+ public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
+ {
+ UninstallPlugin(plugin, removeSettings, true);
+ }
+
+ #endregion
+
+ #region Internal functions
+
+ internal static void InstallPlugin(UserPlugin plugin, string downloadedFilePath, 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));
+ }
+
var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
@@ -420,10 +455,20 @@ namespace Flow.Launcher.Core.Plugin
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
Directory.Delete(pluginFolderPath, true);
+
+ if (checkModified)
+ {
+ _modifiedPlugins.Add(plugin.ID);
+ }
}
- public static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
+ internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified)
{
+ if (checkModified && PluginModified(plugin.ID))
+ {
+ throw new ArgumentException($"Plugin {plugin.Name} has been modified");
+ }
+
if (removeSettings)
{
Settings.Plugins.Remove(plugin.ID);
@@ -432,6 +477,13 @@ namespace Flow.Launcher.Core.Plugin
// Marked for deletion. Will be deleted on next start up
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
+
+ if (checkModified)
+ {
+ _modifiedPlugins.Add(plugin.ID);
+ }
}
+
+ #endregion
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 1daa73261..42a1ac9b8 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
@@ -20,6 +20,7 @@
Error: A plugin which has the same or greater version with {0} already exists.
Error installing plugin
Error occurred while trying to install {0}
+ Error uninstalling plugin
No update available
All plugins are up to date
{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.
@@ -36,7 +37,8 @@
Plugin {0} successfully installed. Please restart Flow.
Plugin {0} successfully uninstalled. Please restart Flow.
Plugin {0} successfully updated. Please restart Flow.
-
+ Plugin {0} has already been modified. Please restart Flow before making any further changes.
+
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 97cfb67d1..6667c3d4d 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -239,8 +239,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- Uninstall(x.PluginExistingMetadata, false);
-
var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
$"{x.Name}-{x.NewVersion}.zip");
@@ -249,7 +247,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
- Install(x.PluginNewUserPlugin, downloadToFilePath);
+ PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath);
if (Settings.AutoRestartAfterChanging)
{
@@ -413,19 +411,24 @@ namespace Flow.Launcher.Plugin.PluginsManager
throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
try
{
- PluginManager.Install(plugin, downloadedFilePath);
+ PluginManager.InstallPlugin(plugin, downloadedFilePath);
}
- catch(FileNotFoundException e)
+ catch (FileNotFoundException e)
{
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
- MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
- catch(InvalidOperationException e)
+ catch (InvalidOperationException e)
{
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name));
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ }
+ catch (ArgumentException e) {
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), plugin.Name));
Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
- MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name),
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"));
}
}
@@ -482,9 +485,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, search);
}
- private static void Uninstall(PluginMetadata plugin, bool removeSettings = true)
+ private void Uninstall(PluginMetadata plugin)
{
- PluginManager.Uninstall(plugin, removeSettings);
+ try
+ {
+ PluginManager.UninstallPlugin(plugin, removeSettings:true);
+ }
+ catch (ArgumentException e)
+ {
+ Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e);
+ Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"),
+ Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"));
+ }
}
}
}
From 53eec760693b78b9d8954791fabc0a4d545c3579 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 15:17:24 +0800
Subject: [PATCH 17/43] Remove unused import
---
.../Flow.Launcher.Plugin.PluginsManager.csproj | 4 ----
1 file changed, 4 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
index 51882a20e..92500ae6a 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
@@ -36,8 +36,4 @@
PreserveNewest
-
-
-
-
\ No newline at end of file
From 50449de653d1fd5bec713eb3544b7a13823d73fc Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 15:29:55 +0800
Subject: [PATCH 18/43] Hide modified plugins in query results
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 6667c3d4d..b04026804 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -188,6 +188,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
on existingPlugin.Metadata.ID equals pluginFromManifest.ID
where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) <
0 // if current version precedes manifest version
+ && !PluginManager.PluginModified(existingPlugin.Metadata.ID)
select
new
{
@@ -317,6 +318,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
var plugin = new UserPlugin
{
+ // FIXME installing in store then install web ver
ID = "",
Name = name,
Version = string.Empty,
@@ -380,7 +382,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
var results =
PluginsManifest
.UserPlugins
- .Where(x => !PluginExists(x.ID))
+ .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
.Select(x =>
new Result
{
From f6a4942a484704a74a5336036f25efe65f53feed Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 16:11:21 +0800
Subject: [PATCH 19/43] Refactor plugin zip logic
- Download zip to temp folder
- Unzip to unique folder
---
Flow.Launcher.Core/Plugin/PluginManager.cs | 39 +++++++++----------
.../PluginsManager.cs | 5 ++-
2 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 64e097379..47edc21c2 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -366,18 +366,28 @@ namespace Flow.Launcher.Core.Plugin
return _modifiedPlugins.Contains(uuid);
}
- public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string downloadedFilePath)
+
+ ///
+ /// Update a plugin to new version, from a zip file. Will Delete zip after updating.
+ ///
+ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
- InstallPlugin(newVersion, downloadedFilePath, checkModified:false);
+ InstallPlugin(newVersion, zipFilePath, checkModified:false);
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
_modifiedPlugins.Add(existingVersion.ID);
}
- public static void InstallPlugin(UserPlugin plugin, string downloadedFilePath)
+ ///
+ /// Install a plugin. Will Delete zip after updating.
+ ///
+ public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
{
- InstallPlugin(plugin, downloadedFilePath, true);
+ InstallPlugin(plugin, zipFilePath, true);
}
+ ///
+ /// Uninstall a plugin.
+ ///
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
{
UninstallPlugin(plugin, removeSettings, true);
@@ -387,7 +397,7 @@ namespace Flow.Launcher.Core.Plugin
#region Internal functions
- internal static void InstallPlugin(UserPlugin plugin, string downloadedFilePath, bool checkModified)
+ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
{
if (checkModified && PluginModified(plugin.ID))
{
@@ -395,21 +405,10 @@ namespace Flow.Launcher.Core.Plugin
throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
}
- var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher");
- var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin");
-
- if (Directory.Exists(tempFolderPath))
- Directory.Delete(tempFolderPath, true);
-
- Directory.CreateDirectory(tempFolderPath);
-
- var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath));
-
- File.Copy(downloadedFilePath, zipFilePath);
-
- File.Delete(downloadedFilePath);
-
+ // Unzip plugin files to temp folder
+ var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
+ File.Delete(zipFilePath);
var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
@@ -454,7 +453,7 @@ namespace Flow.Launcher.Core.Plugin
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
- Directory.Delete(pluginFolderPath, true);
+ Directory.Delete(tempFolderPluginPath, true);
if (checkModified)
{
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index b04026804..1206a4cf9 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -139,7 +139,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
? $"{plugin.Name}-{Guid.NewGuid()}.zip"
: $"{plugin.Name}-{plugin.Version}.zip";
- var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename);
+ var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
try
{
@@ -240,7 +240,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(),
$"{x.Name}-{x.NewVersion}.zip");
_ = Task.Run(async delegate
@@ -414,6 +414,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
PluginManager.InstallPlugin(plugin, downloadedFilePath);
+ File.Delete(downloadedFilePath);
}
catch (FileNotFoundException e)
{
From af9c662892621b3be10d3be76fb1067a20cfd2e1 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 11 Nov 2023 16:21:36 +0800
Subject: [PATCH 20/43] Remove comment
---
Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 1206a4cf9..3cfae97d5 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -318,7 +318,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
var plugin = new UserPlugin
{
- // FIXME installing in store then install web ver
ID = "",
Name = name,
Version = string.Empty,
From 83553244d73024b51b7a5bdc6aee40a0ca369ded Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sat, 11 Nov 2023 18:00:03 -0600
Subject: [PATCH 21/43] use memorypack instead of binaryformatter
---
.../Flow.Launcher.Infrastructure.csproj | 1 +
.../Image/ImageLoader.cs | 70 +-
.../Storage/BinaryStorage.cs | 83 +-
Flow.Launcher/App.xaml.cs | 13 +-
Plugins/Flow.Launcher.Plugin.Program/Main.cs | 35 +-
.../Programs/UWP.cs | 737 -----------------
.../Programs/UWPPackage.cs | 752 ++++++++++++++++++
.../Programs/Win32.cs | 46 +-
.../Views/ProgramSetting.xaml.cs | 35 +-
9 files changed, 899 insertions(+), 873 deletions(-)
delete mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 2f5259039..b24f069c1 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -53,6 +53,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 203c5646a..add6d4e92 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -15,6 +16,7 @@ namespace Flow.Launcher.Infrastructure.Image
public static class ImageLoader
{
private static readonly ImageCache ImageCache = new();
+ private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1);
private static BinaryStorage> _storage;
private static readonly ConcurrentDictionary GuidToKey = new();
private static IImageHashGenerator _hashGenerator;
@@ -25,24 +27,18 @@ namespace Flow.Launcher.Infrastructure.Image
public const int FullIconSize = 256;
- private static readonly string[] ImageExtensions =
- {
- ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"
- };
+ private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
- public static void Initialize()
+ public static async Task InitializeAsync()
{
_storage = new BinaryStorage>("Image");
_hashGenerator = new ImageHashGenerator();
- var usage = LoadStorageToConcurrentDictionary();
+ var usage = await LoadStorageToConcurrentDictionaryAsync();
ImageCache.Initialize(usage.ToDictionary(x => x.Key, x => x.Value));
- foreach (var icon in new[]
- {
- Constant.DefaultIcon, Constant.MissingImgIcon
- })
+ foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
@@ -58,29 +54,41 @@ namespace Flow.Launcher.Infrastructure.Image
await LoadAsync(path, isFullImage);
}
});
- Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
+ Log.Info(
+ $"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
});
}
- public static void Save()
+ public static async Task Save()
{
- lock (_storage)
+ await storageLock.WaitAsync();
+
+ try
{
- _storage.Save(ImageCache.Data
+ _storage.SaveAsync(ImageCache.Data
.ToDictionary(
x => x.Key,
x => x.Value.usage));
}
+ finally
+ {
+ storageLock.Release();
+ }
}
- private static ConcurrentDictionary<(string, bool), int> LoadStorageToConcurrentDictionary()
+ private static async Task> LoadStorageToConcurrentDictionaryAsync()
{
- lock (_storage)
+ await storageLock.WaitAsync();
+ try
{
- var loaded = _storage.TryLoad(new Dictionary<(string, bool), int>());
+ var loaded = await _storage.TryLoadAsync(new Dictionary<(string, bool), int>());
return new ConcurrentDictionary<(string, bool), int>(loaded);
}
+ finally
+ {
+ storageLock.Release();
+ }
}
private class ImageResult
@@ -129,6 +137,7 @@ namespace Flow.Launcher.Infrastructure.Image
ImageCache[path, loadFullImage] = image;
return new ImageResult(image, ImageType.ImageFile);
}
+
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
var imageSource = new BitmapImage(new Uri(path));
@@ -158,6 +167,7 @@ namespace Flow.Launcher.Infrastructure.Image
return imageResult;
}
+
private static async Task LoadRemoteImageAsync(bool loadFullImage, Uri uriResult)
{
// Download image from url
@@ -173,6 +183,7 @@ namespace Flow.Launcher.Infrastructure.Image
image.DecodePixelHeight = SmallIconSize;
image.DecodePixelWidth = SmallIconSize;
}
+
image.StreamSource = buffer;
image.EndInit();
image.StreamSource = null;
@@ -188,8 +199,8 @@ namespace Flow.Launcher.Infrastructure.Image
if (Directory.Exists(path))
{
/* Directories can also have thumbnails instead of shell icons.
- * Generating thumbnails for a bunch of folder results while scrolling
- * could have a big impact on performance and Flow.Launcher responsibility.
+ * Generating thumbnails for a bunch of folder results while scrolling
+ * could have a big impact on performance and Flow.Launcher responsibility.
* - Solution: just load the icon
*/
type = ImageType.Folder;
@@ -208,9 +219,9 @@ namespace Flow.Launcher.Infrastructure.Image
}
else
{
- /* Although the documentation for GetImage on MSDN indicates that
+ /* Although the documentation for GetImage on MSDN indicates that
* if a thumbnail is available it will return one, this has proved to not
- * be the case in many situations while testing.
+ * be the case in many situations while testing.
* - Solution: explicitly pass the ThumbnailOnly flag
*/
image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly);
@@ -236,7 +247,8 @@ namespace Flow.Launcher.Infrastructure.Image
return new ImageResult(image, type);
}
- private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly, int size = SmallIconSize)
+ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly,
+ int size = SmallIconSize)
{
return WindowsThumbnailProvider.GetThumbnail(
path,
@@ -261,17 +273,19 @@ namespace Flow.Launcher.Infrastructure.Image
var img = imageResult.ImageSource;
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
- { // we need to get image hash
+ {
+ // we need to get image hash
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
if (hash != null)
{
-
if (GuidToKey.TryGetValue(hash, out string key))
- { // image already exists
+ {
+ // image already exists
img = ImageCache[key, loadFullImage] ?? img;
}
else
- { // new guid
+ {
+ // new guid
GuidToKey[hash] = path;
}
@@ -289,7 +303,7 @@ namespace Flow.Launcher.Infrastructure.Image
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
- image.UriSource = new Uri(path);
+ image.UriSource = new Uri(path);
image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
image.EndInit();
@@ -314,8 +328,10 @@ namespace Flow.Launcher.Infrastructure.Image
resizedHeight.EndInit();
return resizedHeight;
}
+
return resizedWidth;
}
+
return image;
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
index ea2d42773..a679643fd 100644
--- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
@@ -4,67 +4,65 @@ using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
+using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
+using MemoryPack;
namespace Flow.Launcher.Infrastructure.Storage
{
-#pragma warning disable SYSLIB0011 // BinaryFormatter is obsolete.
///
/// Stroage object using binary data
/// Normally, it has better performance, but not readable
///
+ ///
+ /// It utilize MemoryPack, which means the object must be MemoryPackSerializable
+ /// https://github.com/Cysharp/MemoryPack
+ ///
public class BinaryStorage
{
+ const string DirectoryName = "Cache";
+
+ const string FileSuffix = ".cache";
+
public BinaryStorage(string filename)
{
- const string directoryName = "Cache";
- var directoryPath = Path.Combine(DataLocation.DataDirectory(), directoryName);
+ var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
Helper.ValidateDirectory(directoryPath);
- const string fileSuffix = ".cache";
- FilePath = Path.Combine(directoryPath, $"{filename}{fileSuffix}");
+ FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
}
public string FilePath { get; }
- public T TryLoad(T defaultData)
+ public async ValueTask TryLoadAsync(T defaultData)
{
if (File.Exists(FilePath))
{
if (new FileInfo(FilePath).Length == 0)
{
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
- Save(defaultData);
+ await SaveAsync(defaultData);
return defaultData;
}
- using (var stream = new FileStream(FilePath, FileMode.Open))
- {
- var d = Deserialize(stream, defaultData);
- return d;
- }
+ await using var stream = new FileStream(FilePath, FileMode.Open);
+ var d = await DeserializeAsync(stream, defaultData);
+ return d;
}
else
{
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
- Save(defaultData);
+ await SaveAsync(defaultData);
return defaultData;
}
}
- private T Deserialize(FileStream stream, T defaultData)
+ private async ValueTask DeserializeAsync(Stream stream, T defaultData)
{
- //http://stackoverflow.com/questions/2120055/binaryformatter-deserialize-gives-serializationexception
- AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
- BinaryFormatter binaryFormatter = new BinaryFormatter
- {
- AssemblyFormat = FormatterAssemblyStyle.Simple
- };
-
try
{
- var t = ((T)binaryFormatter.Deserialize(stream)).NonNull();
+ var t = await MemoryPackSerializer.DeserializeAsync(stream);
return t;
}
catch (System.Exception e)
@@ -72,47 +70,12 @@ namespace Flow.Launcher.Infrastructure.Storage
Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
return defaultData;
}
- finally
- {
- AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
- }
}
- private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ public async ValueTask SaveAsync(T data)
{
- Assembly ayResult = null;
- string sShortAssemblyName = args.Name.Split(',')[0];
- Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
- foreach (Assembly ayAssembly in ayAssemblies)
- {
- if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
- {
- ayResult = ayAssembly;
- break;
- }
- }
- return ayResult;
- }
-
- public void Save(T data)
- {
- using (var stream = new FileStream(FilePath, FileMode.Create))
- {
- BinaryFormatter binaryFormatter = new BinaryFormatter
- {
- AssemblyFormat = FormatterAssemblyStyle.Simple
- };
-
- try
- {
- binaryFormatter.Serialize(stream, data);
- }
- catch (SerializationException e)
- {
- Log.Exception($"|BinaryStorage.Save|serialize error for file <{FilePath}>", e);
- }
- }
+ await using var stream = new FileStream(FilePath, FileMode.Create);
+ await MemoryPackSerializer.SerializeAsync(stream, data);
}
}
-#pragma warning restore SYSLIB0011
}
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 295dd3e7a..f4a17761f 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -52,12 +52,13 @@ namespace Flow.Launcher
{
_portable.PreStartCleanUpAfterPortabilityUpdate();
- Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
+ Log.Info(
+ "|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException();
- ImageLoader.Initialize();
+ var imageLoadertask = ImageLoader.InitializeAsync();
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
@@ -78,6 +79,8 @@ namespace Flow.Launcher
Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync(API);
+ await imageLoadertask;
+
var window = new MainWindow(_settings, _mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
@@ -103,7 +106,8 @@ namespace Flow.Launcher
AutoUpdates();
API.SaveAppAllSettings();
- Log.Info("|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
+ Log.Info(
+ "|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
});
}
@@ -122,7 +126,8 @@ namespace Flow.Launcher
// but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false;
- Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message);
+ Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
+ e.Message);
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index ac23534b1..c02573ed8 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -15,24 +15,22 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Plugin.Program
{
- public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, IDisposable
+ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable,
+ IDisposable
{
internal static Win32[] _win32s { get; set; }
- internal static UWP.Application[] _uwps { get; set; }
+ internal static UWPApp[] _uwps { get; set; }
internal static Settings _settings { get; set; }
internal static PluginInitContext Context { get; private set; }
private static BinaryStorage _win32Storage;
- private static BinaryStorage _uwpStorage;
+ private static BinaryStorage _uwpStorage;
private static readonly List emptyResults = new();
- private static readonly MemoryCacheOptions cacheOptions = new()
- {
- SizeLimit = 1560
- };
+ private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
private static MemoryCache cache = new(cacheOptions);
static Main()
@@ -41,8 +39,8 @@ namespace Flow.Launcher.Plugin.Program
public void Save()
{
- _win32Storage.Save(_win32s);
- _uwpStorage.Save(_uwps);
+ _win32Storage.SaveAsync(_win32s);
+ _uwpStorage.SaveAsync(_uwps);
}
public async Task> QueryAsync(Query query, CancellationToken token)
@@ -76,12 +74,12 @@ namespace Flow.Launcher.Plugin.Program
_settings = context.API.LoadSettingJsonStorage();
- Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
+ await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () =>
{
_win32Storage = new BinaryStorage("Win32");
- _win32s = _win32Storage.TryLoad(Array.Empty());
- _uwpStorage = new BinaryStorage("UWP");
- _uwps = _uwpStorage.TryLoad(Array.Empty());
+ _win32s = await _win32Storage.TryLoadAsync(Array.Empty());
+ _uwpStorage = new BinaryStorage("UWP");
+ _uwps = await _uwpStorage.TryLoadAsync(Array.Empty());
});
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
@@ -104,7 +102,7 @@ namespace Flow.Launcher.Plugin.Program
static void WatchProgramUpdate()
{
Win32.WatchProgramUpdate(_settings);
- _ = UWP.WatchPackageChange();
+ _ = UWPPackage.WatchPackageChange();
}
}
@@ -113,16 +111,16 @@ namespace Flow.Launcher.Plugin.Program
var win32S = Win32.All(_settings);
_win32s = win32S;
ResetCache();
- _win32Storage.Save(_win32s);
+ _win32Storage.SaveAsync(_win32s);
_settings.LastIndexTime = DateTime.Now;
}
public static void IndexUwpPrograms()
{
- var applications = UWP.All(_settings);
+ var applications = UWPPackage.All(_settings);
_uwps = applications;
ResetCache();
- _uwpStorage.Save(_uwps);
+ _uwpStorage.SaveAsync(_uwps);
_settings.LastIndexTime = DateTime.Now;
}
@@ -228,7 +226,8 @@ namespace Flow.Launcher.Plugin.Program
catch (Exception)
{
var title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
- var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"), info.FileName);
+ var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"),
+ info.FileName);
Context.API.ShowMsg(title, string.Format(message, info.FileName), string.Empty);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
deleted file mode 100644
index d5924ba28..000000000
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
+++ /dev/null
@@ -1,737 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Security.Principal;
-using System.Threading.Tasks;
-using System.Windows.Media.Imaging;
-using Windows.ApplicationModel;
-using Windows.Management.Deployment;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Plugin.Program.Logger;
-using Flow.Launcher.Plugin.SharedModels;
-using System.Threading.Channels;
-using System.Xml;
-using Windows.ApplicationModel.Core;
-using System.Windows.Input;
-
-namespace Flow.Launcher.Plugin.Program.Programs
-{
- [Serializable]
- public class UWP
- {
- public string Name { get; }
- public string FullName { get; }
- public string FamilyName { get; }
- public string Location { get; set; }
-
- public Application[] Apps { get; set; } = Array.Empty();
-
-
- public UWP(Package package)
- {
- Location = package.InstalledLocation.Path;
- Name = package.Id.Name;
- FullName = package.Id.FullName;
- FamilyName = package.Id.FamilyName;
- }
-
- public void InitAppsInPackage(Package package)
- {
- var apps = new List();
- // WinRT
- var appListEntries = package.GetAppListEntries();
- foreach (var app in appListEntries)
- {
- try
- {
- var tmp = new Application(app, this);
- apps.Add(tmp);
- }
- catch (Exception e)
- {
- ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
- "|Unexpected exception occurs when trying to construct a Application from package"
- + $"{FullName} from location {Location}", e);
- }
- }
- Apps = apps.ToArray();
-
- try
- {
- var xmlDoc = GetManifestXml();
- if (xmlDoc == null)
- {
- return;
- }
-
- var xmlRoot = xmlDoc.DocumentElement;
- var packageVersion = GetPackageVersionFromManifest(xmlRoot);
- if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) ||
- !bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName))
- {
- return;
- }
-
- var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
- namespaceManager.AddNamespace("d", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name
- namespaceManager.AddNamespace("rescap", "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities");
- namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
-
- var allowElevationNode = xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager);
- bool packageCanElevate = allowElevationNode != null;
-
- var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager);
- foreach (var app in Apps)
- {
- // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
- // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
- var id = app.UserModelId.Split('!')[1];
- var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager);
- if (appNode != null)
- {
- app.CanRunElevated = packageCanElevate || Application.IfAppCanRunElevated(appNode);
-
- // local name to fit all versions
- var visualElement = appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager);
- var logoUri = visualElement?.Attributes[logoName]?.Value;
- app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64));
- // use small logo or may have a big margin
- var previewUri = visualElement?.Attributes[logoName]?.Value;
- app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256));
- }
- }
- }
- catch (Exception e)
- {
- ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
- "|Unexpected exception occurs when trying to construct a Application from package"
- + $"{FullName} from location {Location}", e);
- }
- }
-
- private XmlDocument GetManifestXml()
- {
- var manifest = Path.Combine(Location, "AppxManifest.xml");
- try
- {
- var file = File.ReadAllText(manifest);
- var xmlDoc = new XmlDocument();
- xmlDoc.LoadXml(file);
- return xmlDoc;
- }
- catch (FileNotFoundException e)
- {
- ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e);
- return null;
- }
- catch (Exception e)
- {
- ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "An unexpected error occurred and unable to parse AppxManifest.xml", e);
- return null;
- }
- }
-
- private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot)
- {
- if (xmlRoot != null)
- {
-
- var namespaces = xmlRoot.Attributes;
- foreach (XmlAttribute ns in namespaces)
- {
- if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion))
- {
- return packageVersion;
- }
- }
-
- ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
- "|Trying to get the package version of the UWP program, but an unknown UWP app-manifest version in package "
- + $"{FullName} from location {Location}", new FormatException());
- return PackageVersion.Unknown;
- }
- else
- {
- ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
- "|Can't parse AppManifest.xml of package "
- + $"{FullName} from location {Location}", new ArgumentNullException(nameof(xmlRoot)));
- return PackageVersion.Unknown;
- }
- }
-
- private static readonly Dictionary versionFromNamespace = new()
- {
- {
- "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10
- },
- {
- "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81
- },
- {
- "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8
- },
- };
-
- private static readonly Dictionary smallLogoNameFromVersion = new()
- {
- {
- PackageVersion.Windows10, "Square44x44Logo"
- },
- {
- PackageVersion.Windows81, "Square30x30Logo"
- },
- {
- PackageVersion.Windows8, "SmallLogo"
- },
- };
-
- private static readonly Dictionary bigLogoNameFromVersion = new()
- {
- {
- PackageVersion.Windows10, "Square150x150Logo"
- },
- {
- PackageVersion.Windows81, "Square150x150Logo"
- },
- {
- PackageVersion.Windows8, "Logo"
- },
- };
-
- public static Application[] All(Settings settings)
- {
- var support = SupportUWP();
- if (support && settings.EnableUWP)
- {
- var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
- {
- UWP u;
- try
- {
- u = new UWP(p);
- u.InitAppsInPackage(p);
- }
-#if !DEBUG
- catch (Exception e)
- {
- ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e);
- return Array.Empty();
- }
-#endif
-#if DEBUG //make developer aware and implement handling
- catch
- {
- throw;
- }
-#endif
- return u.Apps;
- }).ToArray();
-
- var updatedListWithoutDisabledApps = applications
- .Where(t1 => !Main._settings.DisabledProgramSources
- .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
-
- return updatedListWithoutDisabledApps.ToArray();
- }
- else
- {
- return Array.Empty();
- }
- }
-
- public static bool SupportUWP()
- {
- var windows10 = new Version(10, 0);
- var support = Environment.OSVersion.Version.Major >= windows10.Major;
- return support;
- }
-
- private static IEnumerable CurrentUserPackages()
- {
- var user = WindowsIdentity.GetCurrent().User;
-
- if (user != null)
- {
- var userId = user.Value;
- PackageManager packageManager;
- try
- {
- packageManager = new PackageManager();
- }
- catch
- {
- // Bug from https://github.com/microsoft/CsWinRT, using Microsoft.Windows.SDK.NET.Ref 10.0.19041.0.
- // Only happens on the first time, so a try catch can fix it.
- packageManager = new PackageManager();
- }
- var packages = packageManager.FindPackagesForUser(userId);
- packages = packages.Where(p =>
- {
- try
- {
- var f = p.IsFramework;
- var d = p.IsDevelopmentMode;
- var path = p.InstalledLocation.Path;
- return !f && !d && !string.IsNullOrEmpty(path);
- }
- catch (Exception e)
- {
- ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{p.Id.FullName}", "An unexpected error occurred and "
- + $"unable to verify if package is valid", e);
- return false;
- }
- });
- return packages;
- }
- else
- {
- return Array.Empty();
- }
- }
-
- private static Channel PackageChangeChannel = Channel.CreateBounded(1);
-
- public static async Task WatchPackageChange()
- {
- if (Environment.OSVersion.Version.Major >= 10)
- {
- var catalog = PackageCatalog.OpenForCurrentUser();
- catalog.PackageInstalling += (_, args) =>
- {
- if (args.IsComplete)
- PackageChangeChannel.Writer.TryWrite(default);
- };
- catalog.PackageUninstalling += (_, args) =>
- {
- if (args.IsComplete)
- PackageChangeChannel.Writer.TryWrite(default);
- };
- catalog.PackageUpdating += (_, args) =>
- {
- if (args.IsComplete)
- PackageChangeChannel.Writer.TryWrite(default);
- };
-
- while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
- {
- await Task.Delay(3000).ConfigureAwait(false);
- PackageChangeChannel.Reader.TryRead(out _);
- await Task.Run(Main.IndexUwpPrograms);
- }
-
- }
- }
-
- public override string ToString()
- {
- return FamilyName;
- }
-
- public override bool Equals(object obj)
- {
- if (obj is UWP uwp)
- {
- return FamilyName.Equals(uwp.FamilyName);
- }
- else
- {
- return false;
- }
- }
-
- public override int GetHashCode()
- {
- return FamilyName.GetHashCode();
- }
-
- [Serializable]
- public class Application : IProgram
- {
- private string _uid = string.Empty;
- public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); }
- public string DisplayName { get; set; } = string.Empty;
- public string Description { get; set; } = string.Empty;
- public string UserModelId { get; set; } = string.Empty;
- //public string BackgroundColor { get; set; } = string.Empty; // preserve for future use
- public string Name => DisplayName;
- public string Location { get; set; } = string.Empty;
-
- public bool Enabled { get; set; } = false;
- public bool CanRunElevated { get; set; } = false;
- public string LogoPath { get; set; } = string.Empty;
- public string PreviewImagePath { get; set; } = string.Empty;
-
- public Application(AppListEntry appListEntry, UWP package)
- {
- UserModelId = appListEntry.AppUserModelId;
- UniqueIdentifier = appListEntry.AppUserModelId;
- DisplayName = appListEntry.DisplayInfo.DisplayName;
- Description = appListEntry.DisplayInfo.Description;
- Location = package.Location;
- Enabled = true;
- }
-
- public Result Result(string query, IPublicAPI api)
- {
- string title;
- MatchResult matchResult;
-
- // We suppose Name won't be null
- if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description))
- {
- title = Name;
- matchResult = StringMatcher.FuzzySearch(query, Name);
- }
- else
- {
- title = $"{Name}: {Description}";
- var nameMatch = StringMatcher.FuzzySearch(query, Name);
- var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
- if (descriptionMatch.Score > nameMatch.Score)
- {
- for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
- {
- descriptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
- }
- matchResult = descriptionMatch;
- }
- else
- {
- matchResult = nameMatch;
- }
- }
-
- if (!matchResult.IsSearchPrecisionScoreMet())
- return null;
-
- var result = new Result
- {
- Title = title,
- AutoCompleteText = Name,
- SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
- IcoPath = LogoPath,
- Preview = new Result.PreviewInfo
- {
- IsMedia = false,
- PreviewImagePath = PreviewImagePath,
- Description = Description
- },
- Score = matchResult.Score,
- TitleHighlightData = matchResult.MatchData,
- ContextData = this,
- Action = e =>
- {
- // Ctrl + Enter to open containing folder
- bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control;
- if (openFolder)
- {
- Main.Context.API.OpenDirectory(Location);
- return true;
- }
-
- // Ctrl + Shift + Enter to run elevated
- bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
-
- bool shouldRunElevated = elevated && CanRunElevated;
- _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false);
- if (elevated && !shouldRunElevated)
- {
- var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
- var message = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator_not_supported_message");
- api.ShowMsg(title, message, string.Empty);
- }
-
- return true;
- }
- };
-
-
- return result;
- }
-
- public List ContextMenus(IPublicAPI api)
- {
- var contextMenus = new List
- {
- new Result
- {
- Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
- Action = _ =>
- {
- Main.Context.API.OpenDirectory(Location);
-
- return true;
- },
- IcoPath = "Images/folder.png",
- Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"),
- }
- };
-
- if (CanRunElevated)
- {
- contextMenus.Add(new Result
- {
- Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
- Action = _ =>
- {
- Task.Run(() => Launch(true)).ConfigureAwait(false);
- return true;
- },
- IcoPath = "Images/cmd.png",
- Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef")
- });
- }
-
- return contextMenus;
- }
-
- private void Launch(bool elevated = false)
- {
- string command = "shell:AppsFolder\\" + UserModelId;
- command = Environment.ExpandEnvironmentVariables(command.Trim());
-
- var info = new ProcessStartInfo(command)
- {
- UseShellExecute = true,
- Verb = elevated ? "runas" : ""
- };
-
- Main.StartProcess(Process.Start, info);
- }
-
- internal static bool IfAppCanRunElevated(XmlNode appNode)
- {
- // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
- // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
-
- return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" ||
- appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL";
- }
-
- internal string LogoPathFromUri(string uri, (int, int) desiredSize)
- {
- // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
- // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
- // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
- // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
-
- if (string.IsNullOrWhiteSpace(uri))
- {
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|{UserModelId} 's logo uri is null or empty: {Location}", new ArgumentException("uri"));
- return string.Empty;
- }
-
- string path = Path.Combine(Location, uri);
-
- var pxCount = desiredSize.Item1 * desiredSize.Item2;
- var logoPath = TryToFindLogo(uri, path, pxCount);
- if (logoPath == string.Empty)
- {
- var tmp = Path.Combine(Location, "Assets", uri);
- if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase))
- {
- // TODO: Don't know why, just keep it at the moment
- // Maybe on older version of Windows 10?
- // for C:\Windows\MiracastView etc
- return TryToFindLogo(uri, tmp, pxCount);
- }
- }
- return logoPath;
-
- string TryToFindLogo(string uri, string path, int px)
- {
- var extension = Path.GetExtension(path);
- if (extension != null)
- {
- //if (File.Exists(path))
- //{
- // return path; // shortcut, avoid enumerating files
- //}
-
- var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
- var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
- if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir))
- {
- // Known issue: Edge always triggers it since logo is not at uri
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}", new FileNotFoundException());
- return string.Empty;
- }
-
- var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}");
-
- // Currently we don't care which one to choose
- // Just ignore all qualifiers
- // select like logo.[xxx_yyy].png
- // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
-
- // todo select from file name like pt run
- var selected = logos.FirstOrDefault();
- var closest = selected;
- int min = int.MaxValue;
- foreach (var logo in logos)
- {
-
- var imageStream = File.OpenRead(logo);
- var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
- var height = decoder.Frames[0].PixelHeight;
- var width = decoder.Frames[0].PixelWidth;
- int pixelCountDiff = Math.Abs(height * width - px);
- if (pixelCountDiff < min)
- {
- // try to find the closest to desired size
- closest = logo;
- if (pixelCountDiff == 0)
- break; // found
- min = pixelCountDiff;
- }
- }
-
- selected = closest;
- if (!string.IsNullOrEmpty(selected))
- {
- return selected;
- }
- else
- {
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}", new FileNotFoundException());
- return string.Empty;
- }
- }
- else
- {
- ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
- $"|Unable to find extension from {uri} for {UserModelId} " +
- $"in package location {Location}", new FileNotFoundException());
- return string.Empty;
- }
- }
- }
-
-
- #region logo legacy
- // preserve for potential future use
-
- //public ImageSource Logo()
- //{
- // var logo = ImageFromPath(LogoPath);
- // var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
-
- // // todo magic! temp fix for cross thread object
- // plated.Freeze();
- // return plated;
- //}
- //private BitmapImage ImageFromPath(string path)
- //{
- // if (File.Exists(path))
- // {
- // var image = new BitmapImage();
- // image.BeginInit();
- // image.UriSource = new Uri(path);
- // image.CacheOption = BitmapCacheOption.OnLoad;
- // image.EndInit();
- // image.Freeze();
- // return image;
- // }
- // else
- // {
- // ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Available" : path)}" +
- // $"|Unable to get logo for {UserModelId} from {path} and" +
- // $" located in {Location}", new FileNotFoundException());
- // return new BitmapImage(new Uri(Constant.MissingImgIcon));
- // }
- //}
-
- //private ImageSource PlatedImage(BitmapImage image)
- //{
- // if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
- // {
- // var width = image.Width;
- // var height = image.Height;
- // var x = 0;
- // var y = 0;
-
- // var group = new DrawingGroup();
-
- // var converted = ColorConverter.ConvertFromString(BackgroundColor);
- // if (converted != null)
- // {
- // var color = (Color)converted;
- // var brush = new SolidColorBrush(color);
- // var pen = new Pen(brush, 1);
- // var backgroundArea = new Rect(0, 0, width, width);
- // var rectangle = new RectangleGeometry(backgroundArea);
- // var rectDrawing = new GeometryDrawing(brush, pen, rectangle);
- // group.Children.Add(rectDrawing);
-
- // var imageArea = new Rect(x, y, image.Width, image.Height);
- // var imageDrawing = new ImageDrawing(image, imageArea);
- // group.Children.Add(imageDrawing);
-
- // // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
- // var visual = new DrawingVisual();
- // var context = visual.RenderOpen();
- // context.DrawDrawing(group);
- // context.Close();
- // const int dpiScale100 = 96;
- // var bitmap = new RenderTargetBitmap(
- // Convert.ToInt32(width), Convert.ToInt32(height),
- // dpiScale100, dpiScale100,
- // PixelFormats.Pbgra32
- // );
- // bitmap.Render(visual);
- // return bitmap;
- // }
- // else
- // {
- // ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" +
- // $"|Unable to convert background string {BackgroundColor} " +
- // $"to color for {Location}", new InvalidOperationException());
-
- // return new BitmapImage(new Uri(Constant.MissingImgIcon));
- // }
- // }
- // else
- // {
- // // todo use windows theme as background
- // return image;
- // }
- //}
-
- #endregion
- public override string ToString()
- {
- return $"{DisplayName}: {Description}";
- }
-
- public override bool Equals(object obj)
- {
- if (obj is Application other)
- {
- return UniqueIdentifier == other.UniqueIdentifier;
- }
- else
- {
- return false;
- }
- }
-
- public override int GetHashCode()
- {
- return UniqueIdentifier.GetHashCode();
- }
- }
-
- public enum PackageVersion
- {
- Windows10,
- Windows81,
- Windows8,
- Unknown
- }
- }
-}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
new file mode 100644
index 000000000..3fb22b39d
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
@@ -0,0 +1,752 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Security.Principal;
+using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
+using Windows.ApplicationModel;
+using Windows.Management.Deployment;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Plugin.Program.Logger;
+using Flow.Launcher.Plugin.SharedModels;
+using System.Threading.Channels;
+using System.Xml;
+using Windows.ApplicationModel.Core;
+using System.Windows.Input;
+using MemoryPack;
+
+namespace Flow.Launcher.Plugin.Program.Programs
+{
+ [MemoryPackable]
+ public partial class UWPPackage
+ {
+ public string Name { get; }
+ public string FullName { get; }
+ public string FamilyName { get; }
+ public string Location { get; set; }
+
+ public UWPApp[] Apps { get; set; } = Array.Empty();
+
+
+ ///
+ /// For serialization
+ ///
+ [MemoryPackConstructor]
+ private UWPPackage()
+ {
+ }
+
+ public UWPPackage(Package package)
+ {
+ Location = package.InstalledLocation.Path;
+ Name = package.Id.Name;
+ FullName = package.Id.FullName;
+ FamilyName = package.Id.FamilyName;
+ }
+
+ public void InitAppsInPackage(Package package)
+ {
+ var apps = new List();
+ // WinRT
+ var appListEntries = package.GetAppListEntries();
+ foreach (var app in appListEntries)
+ {
+ try
+ {
+ var tmp = new UWPApp(app, this);
+ apps.Add(tmp);
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
+ "|Unexpected exception occurs when trying to construct a Application from package"
+ + $"{FullName} from location {Location}", e);
+ }
+ }
+
+ Apps = apps.ToArray();
+
+ try
+ {
+ var xmlDoc = GetManifestXml();
+ if (xmlDoc == null)
+ {
+ return;
+ }
+
+ var xmlRoot = xmlDoc.DocumentElement;
+ var packageVersion = GetPackageVersionFromManifest(xmlRoot);
+ if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) ||
+ !bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName))
+ {
+ return;
+ }
+
+ var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
+ namespaceManager.AddNamespace("d",
+ "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name
+ namespaceManager.AddNamespace("rescap",
+ "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities");
+ namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
+
+ var allowElevationNode =
+ xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager);
+ bool packageCanElevate = allowElevationNode != null;
+
+ var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager);
+ foreach (var app in Apps)
+ {
+ // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
+ // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
+ var id = app.UserModelId.Split('!')[1];
+ var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager);
+ if (appNode != null)
+ {
+ app.CanRunElevated = packageCanElevate || UWPApp.IfAppCanRunElevated(appNode);
+
+ // local name to fit all versions
+ var visualElement =
+ appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager);
+ var logoUri = visualElement?.Attributes[logoName]?.Value;
+ app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64));
+ // use small logo or may have a big margin
+ var previewUri = visualElement?.Attributes[logoName]?.Value;
+ app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" +
+ "|Unexpected exception occurs when trying to construct a Application from package"
+ + $"{FullName} from location {Location}", e);
+ }
+ }
+
+ private XmlDocument GetManifestXml()
+ {
+ var manifest = Path.Combine(Location, "AppxManifest.xml");
+ try
+ {
+ var file = File.ReadAllText(manifest);
+ var xmlDoc = new XmlDocument();
+ xmlDoc.LoadXml(file);
+ return xmlDoc;
+ }
+ catch (FileNotFoundException e)
+ {
+ ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e);
+ return null;
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}",
+ "An unexpected error occurred and unable to parse AppxManifest.xml", e);
+ return null;
+ }
+ }
+
+ private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot)
+ {
+ if (xmlRoot != null)
+ {
+ var namespaces = xmlRoot.Attributes;
+ foreach (XmlAttribute ns in namespaces)
+ {
+ if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion))
+ {
+ return packageVersion;
+ }
+ }
+
+ ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
+ "|Trying to get the package version of the UWP program, but an unknown UWP app-manifest version in package "
+ + $"{FullName} from location {Location}", new FormatException());
+ return PackageVersion.Unknown;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" +
+ "|Can't parse AppManifest.xml of package "
+ + $"{FullName} from location {Location}",
+ new ArgumentNullException(nameof(xmlRoot)));
+ return PackageVersion.Unknown;
+ }
+ }
+
+ private static readonly Dictionary versionFromNamespace = new()
+ {
+ { "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 },
+ { "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 },
+ { "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 },
+ };
+
+ private static readonly Dictionary smallLogoNameFromVersion = new()
+ {
+ { PackageVersion.Windows10, "Square44x44Logo" },
+ { PackageVersion.Windows81, "Square30x30Logo" },
+ { PackageVersion.Windows8, "SmallLogo" },
+ };
+
+ private static readonly Dictionary bigLogoNameFromVersion = new()
+ {
+ { PackageVersion.Windows10, "Square150x150Logo" },
+ { PackageVersion.Windows81, "Square150x150Logo" },
+ { PackageVersion.Windows8, "Logo" },
+ };
+
+ public static UWPApp[] All(Settings settings)
+ {
+ var support = SupportUWP();
+ if (support && settings.EnableUWP)
+ {
+ var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
+ {
+ UWPPackage u;
+ try
+ {
+ u = new UWPPackage(p);
+ u.InitAppsInPackage(p);
+ }
+#if !DEBUG
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e);
+ return Array.Empty();
+ }
+#endif
+#if DEBUG //make developer aware and implement handling
+ catch
+ {
+ throw;
+ }
+#endif
+ return u.Apps;
+ }).ToArray();
+
+ var updatedListWithoutDisabledApps = applications
+ .Where(t1 => !Main._settings.DisabledProgramSources
+ .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
+
+ return updatedListWithoutDisabledApps.ToArray();
+ }
+ else
+ {
+ return Array.Empty();
+ }
+ }
+
+ public static bool SupportUWP()
+ {
+ var windows10 = new Version(10, 0);
+ var support = Environment.OSVersion.Version.Major >= windows10.Major;
+ return support;
+ }
+
+ private static IEnumerable CurrentUserPackages()
+ {
+ var user = WindowsIdentity.GetCurrent().User;
+
+ if (user != null)
+ {
+ var userId = user.Value;
+ PackageManager packageManager;
+ try
+ {
+ packageManager = new PackageManager();
+ }
+ catch
+ {
+ // Bug from https://github.com/microsoft/CsWinRT, using Microsoft.Windows.SDK.NET.Ref 10.0.19041.0.
+ // Only happens on the first time, so a try catch can fix it.
+ packageManager = new PackageManager();
+ }
+
+ var packages = packageManager.FindPackagesForUser(userId);
+ packages = packages.Where(p =>
+ {
+ try
+ {
+ var f = p.IsFramework;
+ var d = p.IsDevelopmentMode;
+ var path = p.InstalledLocation.Path;
+ return !f && !d && !string.IsNullOrEmpty(path);
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{p.Id.FullName}",
+ "An unexpected error occurred and "
+ + $"unable to verify if package is valid", e);
+ return false;
+ }
+ });
+ return packages;
+ }
+ else
+ {
+ return Array.Empty();
+ }
+ }
+
+ private static Channel PackageChangeChannel = Channel.CreateBounded(1);
+
+ public static async Task WatchPackageChange()
+ {
+ if (Environment.OSVersion.Version.Major >= 10)
+ {
+ var catalog = PackageCatalog.OpenForCurrentUser();
+ catalog.PackageInstalling += (_, args) =>
+ {
+ if (args.IsComplete)
+ PackageChangeChannel.Writer.TryWrite(default);
+ };
+ catalog.PackageUninstalling += (_, args) =>
+ {
+ if (args.IsComplete)
+ PackageChangeChannel.Writer.TryWrite(default);
+ };
+ catalog.PackageUpdating += (_, args) =>
+ {
+ if (args.IsComplete)
+ PackageChangeChannel.Writer.TryWrite(default);
+ };
+
+ while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false))
+ {
+ await Task.Delay(3000).ConfigureAwait(false);
+ PackageChangeChannel.Reader.TryRead(out _);
+ await Task.Run(Main.IndexUwpPrograms);
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return FamilyName;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is UWPPackage uwp)
+ {
+ return FamilyName.Equals(uwp.FamilyName);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return FamilyName.GetHashCode();
+ }
+
+
+ public enum PackageVersion
+ {
+ Windows10,
+ Windows81,
+ Windows8,
+ Unknown
+ }
+ }
+
+ [MemoryPackable]
+ public partial class UWPApp : IProgram
+ {
+ private string _uid = string.Empty;
+
+ public string UniqueIdentifier
+ {
+ get => _uid;
+ set => _uid = value == null ? string.Empty : value.ToLowerInvariant();
+ }
+
+ public string DisplayName { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+
+ public string UserModelId { get; set; } = string.Empty;
+
+ //public string BackgroundColor { get; set; } = string.Empty; // preserve for future use
+ public string Name => DisplayName;
+ public string Location { get; set; } = string.Empty;
+
+ public bool Enabled { get; set; } = false;
+ public bool CanRunElevated { get; set; } = false;
+ public string LogoPath { get; set; } = string.Empty;
+ public string PreviewImagePath { get; set; } = string.Empty;
+
+ [MemoryPackConstructor]
+ private UWPApp()
+ {
+ }
+
+ public UWPApp(AppListEntry appListEntry, UWPPackage package)
+ {
+ UserModelId = appListEntry.AppUserModelId;
+ UniqueIdentifier = appListEntry.AppUserModelId;
+ DisplayName = appListEntry.DisplayInfo.DisplayName;
+ Description = appListEntry.DisplayInfo.Description;
+ Location = package.Location;
+ Enabled = true;
+ }
+
+ public Result Result(string query, IPublicAPI api)
+ {
+ string title;
+ MatchResult matchResult;
+
+ // We suppose Name won't be null
+ if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description))
+ {
+ title = Name;
+ matchResult = StringMatcher.FuzzySearch(query, Name);
+ }
+ else
+ {
+ title = $"{Name}: {Description}";
+ var nameMatch = StringMatcher.FuzzySearch(query, Name);
+ var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
+ if (descriptionMatch.Score > nameMatch.Score)
+ {
+ for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
+ {
+ descriptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
+ }
+
+ matchResult = descriptionMatch;
+ }
+ else
+ {
+ matchResult = nameMatch;
+ }
+ }
+
+ if (!matchResult.IsSearchPrecisionScoreMet())
+ return null;
+
+ var result = new Result
+ {
+ Title = title,
+ AutoCompleteText = Name,
+ SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
+ IcoPath = LogoPath,
+ Preview = new Result.PreviewInfo
+ {
+ IsMedia = false, PreviewImagePath = PreviewImagePath, Description = Description
+ },
+ Score = matchResult.Score,
+ TitleHighlightData = matchResult.MatchData,
+ ContextData = this,
+ Action = e =>
+ {
+ // Ctrl + Enter to open containing folder
+ bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control;
+ if (openFolder)
+ {
+ Main.Context.API.OpenDirectory(Location);
+ return true;
+ }
+
+ // Ctrl + Shift + Enter to run elevated
+ bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
+
+ bool shouldRunElevated = elevated && CanRunElevated;
+ _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false);
+ if (elevated && !shouldRunElevated)
+ {
+ var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
+ var message =
+ api.GetTranslation(
+ "flowlauncher_plugin_program_run_as_administrator_not_supported_message");
+ api.ShowMsg(title, message, string.Empty);
+ }
+
+ return true;
+ }
+ };
+
+
+ return result;
+ }
+
+ public List ContextMenus(IPublicAPI api)
+ {
+ var contextMenus = new List
+ {
+ new Result
+ {
+ Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
+ Action = _ =>
+ {
+ Main.Context.API.OpenDirectory(Location);
+
+ return true;
+ },
+ IcoPath = "Images/folder.png",
+ Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"),
+ }
+ };
+
+ if (CanRunElevated)
+ {
+ contextMenus.Add(new Result
+ {
+ Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
+ Action = _ =>
+ {
+ Task.Run(() => Launch(true)).ConfigureAwait(false);
+ return true;
+ },
+ IcoPath = "Images/cmd.png",
+ Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef")
+ });
+ }
+
+ return contextMenus;
+ }
+
+ private void Launch(bool elevated = false)
+ {
+ string command = "shell:AppsFolder\\" + UserModelId;
+ command = Environment.ExpandEnvironmentVariables(command.Trim());
+
+ var info = new ProcessStartInfo(command) { UseShellExecute = true, Verb = elevated ? "runas" : "" };
+
+ Main.StartProcess(Process.Start, info);
+ }
+
+ internal static bool IfAppCanRunElevated(XmlNode appNode)
+ {
+ // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
+ // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
+
+ return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" ||
+ appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL";
+ }
+
+ internal string LogoPathFromUri(string uri, (int, int) desiredSize)
+ {
+ // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
+ // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
+ // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
+ // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
+
+ if (string.IsNullOrWhiteSpace(uri))
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|{UserModelId} 's logo uri is null or empty: {Location}",
+ new ArgumentException("uri"));
+ return string.Empty;
+ }
+
+ string path = Path.Combine(Location, uri);
+
+ var pxCount = desiredSize.Item1 * desiredSize.Item2;
+ var logoPath = TryToFindLogo(uri, path, pxCount);
+ if (logoPath == string.Empty)
+ {
+ var tmp = Path.Combine(Location, "Assets", uri);
+ if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase))
+ {
+ // TODO: Don't know why, just keep it at the moment
+ // Maybe on older version of Windows 10?
+ // for C:\Windows\MiracastView etc
+ return TryToFindLogo(uri, tmp, pxCount);
+ }
+ }
+
+ return logoPath;
+
+ string TryToFindLogo(string uri, string path, int px)
+ {
+ var extension = Path.GetExtension(path);
+ if (extension != null)
+ {
+ //if (File.Exists(path))
+ //{
+ // return path; // shortcut, avoid enumerating files
+ //}
+
+ var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
+ var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
+ if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir))
+ {
+ // Known issue: Edge always triggers it since logo is not at uri
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}",
+ new FileNotFoundException());
+ return string.Empty;
+ }
+
+ var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}");
+
+ // Currently we don't care which one to choose
+ // Just ignore all qualifiers
+ // select like logo.[xxx_yyy].png
+ // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
+
+ // todo select from file name like pt run
+ var selected = logos.FirstOrDefault();
+ var closest = selected;
+ int min = int.MaxValue;
+ foreach (var logo in logos)
+ {
+ var imageStream = File.OpenRead(logo);
+ var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile,
+ BitmapCacheOption.None);
+ var height = decoder.Frames[0].PixelHeight;
+ var width = decoder.Frames[0].PixelWidth;
+ int pixelCountDiff = Math.Abs(height * width - px);
+ if (pixelCountDiff < min)
+ {
+ // try to find the closest to desired size
+ closest = logo;
+ if (pixelCountDiff == 0)
+ break; // found
+ min = pixelCountDiff;
+ }
+ }
+
+ selected = closest;
+ if (!string.IsNullOrEmpty(selected))
+ {
+ return selected;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}",
+ new FileNotFoundException());
+ return string.Empty;
+ }
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" +
+ $"|Unable to find extension from {uri} for {UserModelId} " +
+ $"in package location {Location}", new FileNotFoundException());
+ return string.Empty;
+ }
+ }
+ }
+
+
+ #region logo legacy
+
+ // preserve for potential future use
+
+ //public ImageSource Logo()
+ //{
+ // var logo = ImageFromPath(LogoPath);
+ // var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
+
+ // // todo magic! temp fix for cross thread object
+ // plated.Freeze();
+ // return plated;
+ //}
+ //private BitmapImage ImageFromPath(string path)
+ //{
+ // if (File.Exists(path))
+ // {
+ // var image = new BitmapImage();
+ // image.BeginInit();
+ // image.UriSource = new Uri(path);
+ // image.CacheOption = BitmapCacheOption.OnLoad;
+ // image.EndInit();
+ // image.Freeze();
+ // return image;
+ // }
+ // else
+ // {
+ // ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Available" : path)}" +
+ // $"|Unable to get logo for {UserModelId} from {path} and" +
+ // $" located in {Location}", new FileNotFoundException());
+ // return new BitmapImage(new Uri(Constant.MissingImgIcon));
+ // }
+ //}
+
+ //private ImageSource PlatedImage(BitmapImage image)
+ //{
+ // if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
+ // {
+ // var width = image.Width;
+ // var height = image.Height;
+ // var x = 0;
+ // var y = 0;
+
+ // var group = new DrawingGroup();
+
+ // var converted = ColorConverter.ConvertFromString(BackgroundColor);
+ // if (converted != null)
+ // {
+ // var color = (Color)converted;
+ // var brush = new SolidColorBrush(color);
+ // var pen = new Pen(brush, 1);
+ // var backgroundArea = new Rect(0, 0, width, width);
+ // var rectangle = new RectangleGeometry(backgroundArea);
+ // var rectDrawing = new GeometryDrawing(brush, pen, rectangle);
+ // group.Children.Add(rectDrawing);
+
+ // var imageArea = new Rect(x, y, image.Width, image.Height);
+ // var imageDrawing = new ImageDrawing(image, imageArea);
+ // group.Children.Add(imageDrawing);
+
+ // // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
+ // var visual = new DrawingVisual();
+ // var context = visual.RenderOpen();
+ // context.DrawDrawing(group);
+ // context.Close();
+ // const int dpiScale100 = 96;
+ // var bitmap = new RenderTargetBitmap(
+ // Convert.ToInt32(width), Convert.ToInt32(height),
+ // dpiScale100, dpiScale100,
+ // PixelFormats.Pbgra32
+ // );
+ // bitmap.Render(visual);
+ // return bitmap;
+ // }
+ // else
+ // {
+ // ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" +
+ // $"|Unable to convert background string {BackgroundColor} " +
+ // $"to color for {Location}", new InvalidOperationException());
+
+ // return new BitmapImage(new Uri(Constant.MissingImgIcon));
+ // }
+ // }
+ // else
+ // {
+ // // todo use windows theme as background
+ // return image;
+ // }
+ //}
+
+ #endregion
+
+ public override string ToString()
+ {
+ return $"{DisplayName}: {Description}";
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is UWPApp other)
+ {
+ return UniqueIdentifier == other.UniqueIdentifier;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return UniqueIdentifier.GetHashCode();
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 20f489c30..7d08d3670 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -16,14 +16,21 @@ using System.Threading.Channels;
using Flow.Launcher.Plugin.Program.Views.Models;
using IniParser;
using System.Windows.Input;
+using MemoryPack;
namespace Flow.Launcher.Plugin.Program.Programs
{
- [Serializable]
- public class Win32 : IProgram, IEquatable
+ [MemoryPackable]
+ public partial class Win32 : IProgram, IEquatable
{
public string Name { get; set; }
- public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); } // For path comparison
+
+ public string UniqueIdentifier
+ {
+ get => _uid;
+ set => _uid = value == null ? string.Empty : value.ToLowerInvariant();
+ } // For path comparison
+
public string IcoPath { get; set; }
///
@@ -96,7 +103,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName);
string resultName = useLocalizedName ? LocalizedName : Name;
- if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description))
+ if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) ||
+ resultName.Equals(Description))
{
title = resultName;
matchResult = StringMatcher.FuzzySearch(query, resultName);
@@ -113,6 +121,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
}
+
matchResult = descriptionMatch;
}
else
@@ -129,10 +138,12 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
candidates.Add(ExecutableName);
}
+
if (useLocalizedName)
{
candidates.Add(Name);
}
+
matchResult = Match(query, candidates);
if (matchResult == null)
{
@@ -209,9 +220,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
var info = new ProcessStartInfo
{
- FileName = FullPath,
- WorkingDirectory = ParentDirectory,
- UseShellExecute = true
+ FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true
};
Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info));
@@ -424,7 +433,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
- private static IEnumerable EnumerateProgramsInDir(string directory, string[] suffixes, bool recursive = true)
+ private static IEnumerable EnumerateProgramsInDir(string directory, string[] suffixes,
+ bool recursive = true)
{
if (!Directory.Exists(directory))
return Enumerable.Empty();
@@ -448,7 +458,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
- private static IEnumerable UnregisteredPrograms(List directories, string[] suffixes, string[] protocols)
+ private static IEnumerable UnregisteredPrograms(List directories, string[] suffixes,
+ string[] protocols)
{
// Disabled custom sources are not in DisabledProgramSources
var paths = directories.AsParallel()
@@ -466,14 +477,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
.Distinct();
var startupPaths = GetStartupPaths();
-
+
var programs = ExceptDisabledSource(allPrograms)
.Where(x => !startupPaths.Any(startup => FilesFolders.PathContains(startup, x)))
.Select(x => GetProgramFromPath(x, protocols));
return programs;
}
- private static IEnumerable PATHPrograms(string[] suffixes, string[] protocols, List commonParents)
+ private static IEnumerable PATHPrograms(string[] suffixes, string[] protocols,
+ List commonParents)
{
var pathEnv = Environment.GetEnvironmentVariable("Path");
if (String.IsNullOrEmpty(pathEnv))
@@ -515,7 +527,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
var programs = ExceptDisabledSource(toFilter)
- .Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue
+ .Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid)
+ .ToList(); // ToList due to disposing issue
return programs;
}
@@ -616,7 +629,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
.SelectMany(g =>
{
// is shortcut and in start menu
- var startMenu = g.Where(g => g.LnkResolvedPath != null && startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath))).ToList();
+ var startMenu = g.Where(g =>
+ g.LnkResolvedPath != null &&
+ startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath)))
+ .ToList();
if (startMenu.Any())
return startMenu.Take(1);
@@ -756,6 +772,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
while (reader.TryRead(out _))
{
}
+
await Task.Run(Main.IndexWin32Programs);
}
}
@@ -766,6 +783,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
throw new ArgumentException("Path Not Exist");
}
+
var watcher = new FileSystemWatcher(directory);
watcher.Created += static (_, _) => indexQueue.Writer.TryWrite(default);
@@ -804,8 +822,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
parents.Remove(source);
}
}
+
result.AddRange(parents.Select(x => x.Location));
}
+
return result.DistinctBy(x => x.ToLowerInvariant()).ToList();
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
index 156f33ebc..9879993fe 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml.cs
@@ -87,9 +87,9 @@ namespace Flow.Launcher.Plugin.Program.Views
}
}
- public bool ShowUWPCheckbox => UWP.SupportUWP();
+ public bool ShowUWPCheckbox => UWPPackage.SupportUWP();
- public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWP.Application[] uwps)
+ public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWPApp[] uwps)
{
this.context = context;
_settings = settings;
@@ -149,9 +149,9 @@ namespace Flow.Launcher.Plugin.Program.Views
private void DeleteProgramSources(List itemsToDelete)
{
itemsToDelete.ForEach(t1 => _settings.ProgramSources
- .Remove(_settings.ProgramSources
- .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier)
- .FirstOrDefault()));
+ .Remove(_settings.ProgramSources
+ .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier)
+ .FirstOrDefault()));
itemsToDelete.ForEach(x => ProgramSettingDisplayList.Remove(x));
ReIndexing();
@@ -182,16 +182,20 @@ namespace Flow.Launcher.Plugin.Program.Views
{
if (selectedProgramSource.Enabled)
{
- ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, true); // sync status in win32, uwp and disabled
+ ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource },
+ true); // sync status in win32, uwp and disabled
ProgramSettingDisplay.RemoveDisabledFromSettings();
}
else
{
- ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, false);
+ ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource },
+ false);
ProgramSettingDisplay.StoreDisabledInSettings();
}
+
ReIndexing();
}
+
programSourceView.SelectedIndex = selectedIndex;
}
}
@@ -233,7 +237,8 @@ namespace Flow.Launcher.Plugin.Program.Views
foreach (string directory in directories)
{
if (Directory.Exists(directory)
- && !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase)))
+ && !ProgramSettingDisplayList.Any(x =>
+ x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase)))
{
var source = new ProgramSource(directory);
@@ -262,8 +267,8 @@ namespace Flow.Launcher.Plugin.Program.Views
private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
{
var selectedItems = programSourceView
- .SelectedItems.Cast()
- .ToList();
+ .SelectedItems.Cast()
+ .ToList();
if (selectedItems.Count == 0)
{
@@ -274,7 +279,8 @@ namespace Flow.Launcher.Plugin.Program.Views
if (IsAllItemsUserAdded(selectedItems))
{
- var msg = string.Format(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
+ var msg = string.Format(
+ context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
@@ -364,8 +370,8 @@ namespace Flow.Launcher.Plugin.Program.Views
private void programSourceView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItems = programSourceView
- .SelectedItems.Cast()
- .ToList();
+ .SelectedItems.Cast()
+ .ToList();
if (IsAllItemsUserAdded(selectedItems))
{
@@ -400,7 +406,8 @@ namespace Flow.Launcher.Plugin.Program.Views
ListView listView = sender as ListView;
GridView gView = listView.View as GridView;
- var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
+ var workingWidth =
+ listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
var col1 = 0.25;
var col2 = 0.15;
var col3 = 0.60;
From c18ae41e56f5b38ca4d702e58fa691e29ad7743d Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sun, 12 Nov 2023 11:42:13 +0800
Subject: [PATCH 22/43] Delete existing zip before downloading
---
.../PluginsManager.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 3cfae97d5..00f77f872 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -143,6 +143,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
try
{
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+
await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
Install(plugin, filePath);
@@ -245,6 +250,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
_ = Task.Run(async delegate
{
+ if (File.Exists(downloadToFilePath))
+ {
+ File.Delete(downloadToFilePath);
+ }
+
await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath)
.ConfigureAwait(false);
From 3ec27edf75fc70fecc9d8d2865fc0f5c9579f557 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sat, 18 Nov 2023 14:22:54 -0600
Subject: [PATCH 23/43] Use ConcurrentDictionary for JsonRPC Settings
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index b87623c56..fd73a44cd 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -16,10 +17,10 @@ namespace Flow.Launcher.Core.Plugin
public Dictionary SettingControls { get; } = new();
public IReadOnlyDictionary Inner => Settings;
- protected Dictionary Settings { get; set; }
+ protected ConcurrentDictionary Settings { get; set; }
public required IPublicAPI API { get; init; }
- private JsonStorage> _storage;
+ private JsonStorage> _storage;
// maybe move to resource?
private static readonly Thickness settingControlMargin = new(0, 9, 18, 9);
@@ -33,7 +34,7 @@ namespace Flow.Launcher.Core.Plugin
public async Task InitializeAsync()
{
- _storage = new JsonStorage>(SettingPath);
+ _storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
foreach (var (type, attributes) in Configuration.Body)
From 1cafae827889ac0520cd16e06512d447e3bcf604 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:43:17 -0500
Subject: [PATCH 24/43] Allow nullable for Configuration
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index b87623c56..1b14597a4 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -10,7 +10,7 @@ namespace Flow.Launcher.Core.Plugin
{
public class JsonRPCPluginSettings
{
- public required JsonRpcConfigurationModel Configuration { get; init; }
+ public required JsonRpcConfigurationModel? Configuration { get; init; }
public required string SettingPath { get; init; }
public Dictionary SettingControls { get; } = new();
From 93100c0330b19d4861d6bb466f7468b8405866b6 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:43:42 -0500
Subject: [PATCH 25/43] Remove missing template file short circuit logic
---
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 330120c12..197932f04 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -126,8 +126,6 @@ namespace Flow.Launcher.Core.Plugin
private async Task InitSettingAsync()
{
- if (!File.Exists(SettingConfigurationPath))
- return;
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
From b67e815022416cc30b8aae926ef9216a08eecfa7 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:44:01 -0500
Subject: [PATCH 26/43] Load template file only if exists
---
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 197932f04..c6b56c81d 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -126,12 +126,15 @@ namespace Flow.Launcher.Core.Plugin
private async Task InitSettingAsync()
{
+ JsonRpcConfigurationModel configuration = null;
+ if (File.Exists(SettingConfigurationPath))
+ {
+ var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
+ configuration =
+ deserializer.Deserialize(
+ await File.ReadAllTextAsync(SettingConfigurationPath));
+ }
- var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
- .Build();
- var configuration =
- deserializer.Deserialize(
- await File.ReadAllTextAsync(SettingConfigurationPath));
Settings ??= new JsonRPCPluginSettings
{
From 388688e89c26d74490ac377e17f41a2b7765241e Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:44:27 -0500
Subject: [PATCH 27/43] Short circuit template UI process if doesn't exist
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 1b14597a4..e26f5e7a7 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -36,6 +36,11 @@ namespace Flow.Launcher.Core.Plugin
_storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
+ if (Settings != null)
+ {
+ return;
+ }
+
foreach (var (type, attributes) in Configuration.Body)
{
if (attributes.Name == null)
From ba9aba2bff27a94810496be498271d244bf1538c Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:45:16 -0500
Subject: [PATCH 28/43] Allow new setting keys to be instantiated
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index e26f5e7a7..842a91919 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -63,10 +63,7 @@ namespace Flow.Launcher.Core.Plugin
foreach (var (key, value) in settings)
{
- if (Settings.ContainsKey(key))
- {
- Settings[key] = value;
- }
+ Settings[key] = value;
if (SettingControls.TryGetValue(key, out var control))
{
From 5c90946a6ee99df8973096fdba3d8d69b7b3617b Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 18:45:24 -0500
Subject: [PATCH 29/43] Save to file on update
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 842a91919..06d9f8dec 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -83,6 +83,7 @@ namespace Flow.Launcher.Core.Plugin
break;
}
}
+ Save();
}
}
From a86e7bcaa9ed3e9a0b456bea6708f0e8b24e1442 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sat, 18 Nov 2023 23:15:39 -0500
Subject: [PATCH 30/43] Remove save function from loop
---
Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 06d9f8dec..43215bdd5 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -83,8 +83,8 @@ namespace Flow.Launcher.Core.Plugin
break;
}
}
- Save();
}
+ Save();
}
public async Task SaveAsync()
From 798d30ea27aa24da41e569da4b4d21bd76e36a3d Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sun, 19 Nov 2023 17:04:29 +0800
Subject: [PATCH 31/43] Ignore modifier key when using key + number to launch
result
- close #2191
- close #2425
---
Flow.Launcher/ViewModel/MainViewModel.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index c832c258d..7dcd2f4d2 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -285,7 +285,8 @@ namespace Flow.Launcher.ViewModel
}
var hideWindow = await result.ExecuteAsync(new ActionContext
{
- SpecialKeyState = GlobalHotkey.CheckModifiers()
+ // not null means pressing modifier key + number, should ignore the modifier key
+ SpecialKeyState = index is not null ? new SpecialKeyState() : GlobalHotkey.CheckModifiers()
})
.ConfigureAwait(false);
From b63c4eb2bfea0e80d1d0ac7e21f2e16e558a1686 Mon Sep 17 00:00:00 2001
From: Garulf <535299+Garulf@users.noreply.github.com>
Date: Sun, 19 Nov 2023 09:11:27 -0500
Subject: [PATCH 32/43] Revert SettingsChanges to SettingsChange for backwards
compatibility
---
Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 2 +-
Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
index 48606eea4..5b2a9f6cb 100644
--- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
+++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
public record JsonRPCQueryResponseModel(int Id,
[property: JsonPropertyName("result")] List Result,
- IReadOnlyDictionary SettingsChanges = null,
+ IReadOnlyDictionary SettingsChange = null,
string DebugMessage = "",
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
index 330120c12..b0075c8f0 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
@@ -94,7 +94,7 @@ namespace Flow.Launcher.Core.Plugin
results.AddRange(queryResponseModel.Result);
- Settings?.UpdateSettings(queryResponseModel.SettingsChanges);
+ Settings?.UpdateSettings(queryResponseModel.SettingsChange);
return results;
}
From 6625e911829a42c0d4115ef6a8c5db2dad8438df Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 20 Nov 2023 23:20:59 +0800
Subject: [PATCH 33/43] Use default SpecialKeyState
---
Flow.Launcher.Plugin/ActionContext.cs | 7 +++++++
Flow.Launcher/ViewModel/MainViewModel.cs | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs
index d6ba4894e..e31c8e31d 100644
--- a/Flow.Launcher.Plugin/ActionContext.cs
+++ b/Flow.Launcher.Plugin/ActionContext.cs
@@ -50,5 +50,12 @@ namespace Flow.Launcher.Plugin
(AltPressed ? ModifierKeys.Alt : ModifierKeys.None) |
(WinPressed ? ModifierKeys.Windows : ModifierKeys.None);
}
+
+ public static readonly SpecialKeyState Default = new () {
+ CtrlPressed = false,
+ ShiftPressed = false,
+ AltPressed = false,
+ WinPressed = false
+ };
}
}
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index 7dcd2f4d2..61bf0c4dc 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -286,7 +286,7 @@ namespace Flow.Launcher.ViewModel
var hideWindow = await result.ExecuteAsync(new ActionContext
{
// not null means pressing modifier key + number, should ignore the modifier key
- SpecialKeyState = index is not null ? new SpecialKeyState() : GlobalHotkey.CheckModifiers()
+ SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers()
})
.ConfigureAwait(false);
From bf1e451351529ae4a65598faa0da6a9f376f4de7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 22:19:24 +0000
Subject: [PATCH 34/43] Bump System.Drawing.Common from 7.0.0 to 8.0.0
Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/dotnet/winforms/releases)
- [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md)
- [Commits](https://github.com/dotnet/winforms/commits/v8.0.0)
---
updated-dependencies:
- dependency-name: System.Drawing.Common
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Infrastructure.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 2f5259039..8124a95de 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -58,7 +58,7 @@
-
+
From 6a302c928a3c15d5a97d00e69569413db7650f94 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 22:19:33 +0000
Subject: [PATCH 35/43] Bump Microsoft.NET.Test.Sdk from 17.7.2 to 17.8.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.2 to 17.8.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.7.2...v17.8.0)
---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
Flow.Launcher.Test/Flow.Launcher.Test.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index c662fdeff..29414baa6 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -54,7 +54,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
From a04bcce91e3dfbbfb98de891a118f0e1375c2a75 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 22:19:39 +0000
Subject: [PATCH 36/43] Bump Microsoft.Data.Sqlite from 7.0.13 to 8.0.0
Bumps [Microsoft.Data.Sqlite](https://github.com/dotnet/efcore) from 7.0.13 to 8.0.0.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v7.0.13...v8.0.0)
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Plugin.BrowserBookmark.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index edaa3dd29..4ce8584fc 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -56,7 +56,7 @@
-
+
\ No newline at end of file
From 6e385a3d7c622e438afd0747a690b11e634ab739 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Sat, 25 Nov 2023 01:20:43 +0800
Subject: [PATCH 37/43] Revert "Bump System.Drawing.Common from 7.0.0 to 8.0.0"
---
.../Flow.Launcher.Infrastructure.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 8124a95de..2f5259039 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -58,7 +58,7 @@
-
+
From fd9e8a59e116a8eb53dc1126066287c8cd803be7 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sat, 25 Nov 2023 23:57:05 -0600
Subject: [PATCH 38/43] fix build
---
Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
index 3fb22b39d..654897cc5 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
@@ -214,7 +214,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e)
{
ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e);
- return Array.Empty();
+ return Array.Empty();
}
#endif
#if DEBUG //make developer aware and implement handling
From 44fb863f075a4227b3d4b026340ab9d90040b270 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Nov 2023 09:33:34 -0600
Subject: [PATCH 39/43] minor fix jsonrpc errorstream and expect.txt
---
.github/actions/spelling/expect.txt | 4 ----
Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs | 4 +++-
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index d0fee9559..f2be7fb3b 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -1,7 +1,6 @@
crowdin
DWM
workflows
-Wpf
wpf
actionkeyword
stackoverflow
@@ -20,9 +19,7 @@ Prioritise
Segoe
Google
Customise
-UWP
uwp
-Uwp
Bokmal
Bokm
uninstallation
@@ -61,7 +58,6 @@ popup
ptr
pluginindicator
TobiasSekan
-Img
img
resx
bak
diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
index 24d06d975..a476f06e9 100644
--- a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
@@ -17,7 +17,7 @@ namespace Flow.Launcher.Core.Plugin
protected abstract ProcessStartInfo StartInfo { get; set; }
- public Process ClientProcess { get; set; }
+ protected Process ClientProcess { get; set; }
public override async Task InitAsync(PluginInitContext context)
{
@@ -33,6 +33,8 @@ namespace Flow.Launcher.Core.Plugin
SetupPipe(ClientProcess);
+ ErrorStream = ClientProcess.StandardError;
+
await base.InitAsync(context);
}
From 1bd16cccaf67ceeafb6bf76febfd2348a2e0fcd4 Mon Sep 17 00:00:00 2001
From: Hongtao Zhang
Date: Sun, 26 Nov 2023 09:37:43 -0600
Subject: [PATCH 40/43] remove duplicate expect
---
.github/actions/spelling/expect.txt | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index f2be7fb3b..0d4dde36b 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -74,7 +74,6 @@ WCA_ACCENT_POLICY
HGlobal
dopusrt
firefox
-Firefox
msedge
svgc
ime
@@ -83,7 +82,6 @@ txb
btn
otf
searchplugin
-Noresult
wpftk
mkv
flac
From c8753b29ec4f8001b1743fdb960bef4105dcf526 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 27 Nov 2023 21:50:23 +0800
Subject: [PATCH 41/43] Set default value of `AutoRestartAfterChanging` to
`true`
---
Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
index f23ff71f0..811bec50c 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
@@ -10,6 +10,6 @@
public bool WarnFromUnknownSource { get; set; } = true;
- public bool AutoRestartAfterChanging { get; set; } = false;
+ public bool AutoRestartAfterChanging { get; set; } = true;
}
}
From 44af8e59f9b319c345cbc09f9bd900a8df52011a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:00:55 +0000
Subject: [PATCH 42/43] Bump System.Data.OleDb from 7.0.0 to 8.0.0
Bumps [System.Data.OleDb](https://github.com/dotnet/runtime) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0)
---
updated-dependencies:
- dependency-name: System.Data.OleDb
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.../Flow.Launcher.Plugin.Explorer.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
index 1c0bdaad7..6d1497327 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
@@ -45,7 +45,7 @@
-
+
From 7a603f5504b22edd4b959a7b81fef5ff47afc692 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Wed, 6 Dec 2023 22:42:24 +0800
Subject: [PATCH 43/43] Fix spell check
- fix crash
- fix missing dict
---
.github/actions/spelling/expect.txt | 4 +++-
.github/actions/spelling/patterns.txt | 3 +++
.github/workflows/spelling.yml | 7 +++----
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 0d4dde36b..8e29be550 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -102,4 +102,6 @@ Preinstalled
errormetadatafile
noresult
pluginsmanager
-alreadyexists
\ No newline at end of file
+alreadyexists
+JsonRPC
+JsonRPCV2
diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt
index 903714aef..f29f57ad5 100644
--- a/.github/actions/spelling/patterns.txt
+++ b/.github/actions/spelling/patterns.txt
@@ -118,3 +118,6 @@
# UWP
[Uu][Ww][Pp]
+
+# version suffix v#
+(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index 97d3cccb3..7aaa9296a 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -73,7 +73,7 @@ jobs:
steps:
- name: check-spelling
id: spelling
- uses: check-spelling/check-spelling@v0.0.22
+ uses: check-spelling/check-spelling@prerelease
with:
suppress_push_for_open_pull_request: 1
checkout: true
@@ -91,10 +91,9 @@ jobs:
extra_dictionaries:
cspell:software-terms/dict/softwareTerms.txt
cspell:win32/src/win32.txt
- cspell:php/src/php.txt
cspell:filetypes/filetypes.txt
cspell:csharp/csharp.txt
- cspell:dotnet/src/dotnet.txt
+ cspell:dotnet/dict/dotnet.txt
cspell:python/src/common/extra.txt
cspell:python/src/python/python-lib.txt
cspell:aws/aws.txt
@@ -130,7 +129,7 @@ jobs:
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
- uses: check-spelling/check-spelling@v0.0.22
+ uses: check-spelling/check-spelling@prerelease
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@main