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/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..f6e5e5879 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;
}
@@ -126,14 +126,15 @@ namespace Flow.Launcher.Core.Plugin
private async Task InitSettingAsync()
{
- if (!File.Exists(SettingConfigurationPath))
- return;
+ 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
{
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index b87623c56..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;
@@ -10,16 +11,16 @@ 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();
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,9 +34,14 @@ namespace Flow.Launcher.Core.Plugin
public async Task InitializeAsync()
{
- _storage = new JsonStorage>(SettingPath);
+ _storage = new JsonStorage>(SettingPath);
Settings = await _storage.LoadAsync();
+ if (Settings != null)
+ {
+ return;
+ }
+
foreach (var (type, attributes) in Configuration.Body)
{
if (attributes.Name == null)
@@ -58,10 +64,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))
{
@@ -82,6 +85,7 @@ namespace Flow.Launcher.Core.Plugin
}
}
}
+ Save();
}
public async Task SaveAsync()
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 305ba9b65..60130843e 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -91,7 +91,7 @@ namespace Flow.Launcher.Core.Plugin
private void SetupJsonRPC()
{
- var formatter = new SystemTextJsonFormatter();
+ var formatter = new JsonMessageFormatter();
var handler = new NewLineDelimitedMessageHandler(ClientPipe,
formatter);
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/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
index 1f28f5d32..76233c3a4 100644
--- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
+++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
@@ -67,7 +67,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index 3cd9e3df7..29414baa6 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -49,12 +49,12 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index e7b35e689..53c1bafc7 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -96,7 +96,7 @@
-
+
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..4ce8584fc 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.Explorer/Search/QuickAccessLinks/QuickAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
index cdd2c93e6..85b595390 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
@@ -13,20 +13,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
if (string.IsNullOrEmpty(query.Search))
return new List();
- string search = query.Search.ToLower();
-
- var queriedAccessLinks =
- accessLinks
- .Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || x.Path.Contains(search, StringComparison.OrdinalIgnoreCase))
+ return accessLinks
+ .Where(x => Main.Context.API.FuzzySearch(query.Search, x.Name).IsSearchPrecisionScoreMet() || Main.Context.API.FuzzySearch(query.Search, x.Path).IsSearchPrecisionScoreMet())
.OrderBy(x => x.Type)
- .ThenBy(x => x.Name);
-
- return queriedAccessLinks.Select(l => l.Type switch
- {
- ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
- ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
- _ => throw new ArgumentOutOfRangeException()
- }).ToList();
+ .ThenBy(x => x.Name)
+ .Select(l => l.Type switch
+ {
+ ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
+ ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
+ _ => throw new ArgumentOutOfRangeException()
+ })
+ .ToList();
}
internal static List AccessLinkListAll(Query query, IEnumerable accessLinks)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
index 7dfa1656c..53d4049e6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
@@ -10,7 +10,7 @@
"Name": "Explorer",
"Description": "Find and manage files and folders via Windows Search or Everything",
"Author": "Jeremy Wu",
- "Version": "3.1.3",
+ "Version": "3.1.4",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
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 @@
+
+
+
+
+