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 @@
-
+
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index 43215bdd5..3ffac1343 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();
if (Settings != null)
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index f8c9a3f17..47edc21c2 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -11,6 +11,8 @@ 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 System.Text.Json;
namespace Flow.Launcher.Core.Plugin
{
@@ -27,9 +29,9 @@ 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;
+ private static List _modifiedPlugins = new List();
///
/// Directories that will hold Flow Launcher plugin directory
@@ -331,5 +333,156 @@ 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);
+ }
+
+ #region Public functions
+
+ public static bool PluginModified(string uuid)
+ {
+ return _modifiedPlugins.Contains(uuid);
+ }
+
+
+ ///
+ /// 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, zipFilePath, checkModified:false);
+ UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
+ _modifiedPlugins.Add(existingVersion.ID);
+ }
+
+ ///
+ /// Install a plugin. Will Delete zip after updating.
+ ///
+ public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
+ {
+ InstallPlugin(plugin, zipFilePath, true);
+ }
+
+ ///
+ /// Uninstall a plugin.
+ ///
+ public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
+ {
+ UninstallPlugin(plugin, removeSettings, true);
+ }
+
+ #endregion
+
+ #region Internal functions
+
+ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
+ {
+ if (checkModified && PluginModified(plugin.ID))
+ {
+ // Distinguish exception from installing same or less version
+ throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
+ }
+
+ // 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);
+
+ 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(tempFolderPluginPath, true);
+
+ if (checkModified)
+ {
+ _modifiedPlugins.Add(plugin.ID);
+ }
+ }
+
+ 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);
+ 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"));
+
+ if (checkModified)
+ {
+ _modifiedPlugins.Add(plugin.ID);
+ }
+ }
+
+ #endregion
}
}
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
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");
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
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml
index 1f74a49a2..42a1ac9b8 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}
@@ -18,19 +20,25 @@
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.
+ {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 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
@@ -48,4 +56,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 683904ea0..00f77f872 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;
@@ -117,9 +116,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)
@@ -130,36 +139,48 @@ 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
{
- await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"),
- string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), plugin.Name));
+ await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false);
Install(plugin, filePath);
}
- catch (Exception e)
+ catch (HttpRequestException e)
{
- 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");
-
+ 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;
}
-
- 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();
+ 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));
+ Log.Exception("PluginsManager", "An error occurred while downloading plugin", e);
+ return;
+ }
+
+ 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)
@@ -172,6 +193,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
{
@@ -204,36 +226,57 @@ 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"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- Uninstall(x.PluginExistingMetadata, false);
-
- var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory,
+ var downloadToFilePath = Path.Combine(Path.GetTempPath(),
$"{x.Name}-{x.NewVersion}.zip");
- Task.Run(async delegate
+ _ = Task.Run(async delegate
{
+ if (File.Exists(downloadToFilePath))
+ {
+ File.Delete(downloadToFilePath);
+ }
+
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));
+ PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath);
- 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}",
- t.Exception.InnerException, "RequestUpdate");
+ t.Exception.InnerException);
Context.API.ShowMsg(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
@@ -348,12 +391,13 @@ namespace Flow.Launcher.Plugin.PluginsManager
var results =
PluginsManifest
.UserPlugins
+ .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID))
.Select(x =>
new Result
{
Title = $"{x.Name} by {x.Author}",
SubTitle = x.Description,
- IcoPath = icoPath,
+ IcoPath = x.IcoPath,
Action = e =>
{
if (e.SpecialKeyState.CtrlPressed)
@@ -375,78 +419,29 @@ namespace Flow.Launcher.Plugin.PluginsManager
private void Install(UserPlugin plugin, string downloadedFilePath)
{
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))
+ throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath);
+ try
{
- 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));
+ PluginManager.InstallPlugin(plugin, downloadedFilePath);
+ File.Delete(downloadedFilePath);
}
-
- if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
+ catch (FileNotFoundException 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.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);
+ }
+ 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);
}
-
- 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)
@@ -461,10 +456,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"),
@@ -472,7 +476,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;
}
@@ -484,24 +497,18 @@ namespace Flow.Launcher.Plugin.PluginsManager
return Search(results, search);
}
- private void Uninstall(PluginMetadata plugin, bool removedSetting = true)
+ private void Uninstall(PluginMetadata plugin)
{
- if (removedSetting)
+ try
{
- PluginManager.Settings.Plugins.Remove(plugin.ID);
- PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
+ 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"));
}
-
- // 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);
}
}
}
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 @@
+
+
+
+
+