From 2e8c91968f5f2fc4bc19c6a0d6912b88ae3109e2 Mon Sep 17 00:00:00 2001 From: Florian Grabmeier Date: Thu, 1 Feb 2024 16:38:44 +0100 Subject: [PATCH 1/4] Add local plugin installation Signed-off-by: Florian Grabmeier --- .../PluginsManager.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 8cd58ac5212..b9eaea2d412 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -14,6 +14,8 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.IO.Compression; +using Newtonsoft.Json; namespace Flow.Launcher.Plugin.PluginsManager { @@ -138,6 +140,12 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin) MessageBoxButton.YesNo) == MessageBoxResult.No) return; + if (File.Exists(plugin.UrlDownload)) + { + Install(plugin, plugin.UrlDownload); + return; + } + // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download var downloadFilename = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}.zip" @@ -470,6 +478,54 @@ internal List InstallFromWeb(string url) return new List { result }; } + internal List InstallFromLocal(string path) + { + if (string.IsNullOrEmpty(path) || !File.Exists(path)) + { + return new List(); + } + + var plugin = null as UserPlugin; + + using (ZipArchive archive = ZipFile.OpenRead(path)) + { + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); + ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); + + if (pluginJsonEntry != null) + { + using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + { + string pluginJsonContent = reader.ReadToEnd(); + plugin = JsonConvert.DeserializeObject(pluginJsonContent); + plugin.IcoPath = Path.Combine(path, pluginJsonEntry.FullName.Split('/')[0], plugin.IcoPath); + } + } + } + + if (plugin == null) + { + return new List(); + } + + plugin.UrlDownload = path; + + var result = new Result + { + Title = plugin.Name, + SubTitle = plugin.UrlDownload, + IcoPath = plugin.IcoPath, + Action = e => + { + Application.Current.MainWindow.Hide(); + _ = InstallOrUpdateAsync(plugin); + + return ShouldHideWindow; + } + }; + return new List { result }; + } + private bool InstallSourceKnown(string url) { var author = url.Split('/')[3]; @@ -489,6 +545,9 @@ internal async ValueTask> RequestInstallOrUpdate(string search, Can && search.Split('.').Last() == zip) return InstallFromWeb(search); + if (File.Exists(search) && search.Split('.').Last() == zip) + return InstallFromLocal(search); + var results = PluginsManifest .UserPlugins From 1dd526a2f984e1876f912a4d6a27f3f753f7a33e Mon Sep 17 00:00:00 2001 From: Florian Grabmeier Date: Sun, 4 Feb 2024 17:29:05 +0100 Subject: [PATCH 2/4] Add update from local file Signed-off-by: Florian Grabmeier --- .../Images/zipfolder.png | Bin 0 -> 2939 bytes .../PluginsManager.cs | 126 +++++++++++++++++- 2 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png new file mode 100644 index 0000000000000000000000000000000000000000..5d3ed0ace3f8a26662b82611eccd95463f32f8c8 GIT binary patch literal 2939 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 RequestUpdateAsync(string search, Cancell { await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); + if (File.Exists(search) && search.Split('.').Last() == zip) + { + var plugin = null as UserPlugin; + + using (ZipArchive archive = ZipFile.OpenRead(search)) + { + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); + ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); + + if (pluginJsonEntry != null) + { + using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + { + string pluginJsonContent = await reader.ReadToEndAsync(); + plugin = JsonConvert.DeserializeObject(pluginJsonContent); + plugin.IcoPath = "Images\\zipfolder.png"; + } + } + } + if (plugin == null) + { + return new List + { + new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), + IcoPath = icoPath + } + }; + } + + var pluginOld = Context.API.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == plugin.ID); + + return new List { + new Result + { + + Title = $"{plugin.Name} by {plugin.Author}", + SubTitle = $"Update from version {pluginOld.Metadata.Version} to {plugin.Version}", + IcoPath = pluginOld.Metadata.IcoPath, + Action = e => + { + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"), + plugin.Name, plugin.Author, + Environment.NewLine); + } + + if (MessageBox.Show(message, + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) != MessageBoxResult.Yes) + { + return false; + } + + var downloadToFilePath = search; + + + PluginManager.UpdatePlugin(pluginOld.Metadata, plugin, + downloadToFilePath); + + if (Settings.AutoRestartAfterChanging) + { + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format( + Context.API.GetTranslation( + "plugin_pluginsmanager_update_success_restart"), + plugin.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"), + plugin.Name)); + } + + return true; + + }, + ContextData = + new UserPlugin + { + Website = plugin.Website, + UrlSourceCode = plugin.UrlSourceCode + } + }}; + } + var resultsForUpdate = ( from existingPlugin in Context.API.GetAllPlugins() join pluginFromManifest in PluginsManifest.UserPlugins @@ -498,7 +610,7 @@ internal List InstallFromLocal(string path) { string pluginJsonContent = reader.ReadToEnd(); plugin = JsonConvert.DeserializeObject(pluginJsonContent); - plugin.IcoPath = Path.Combine(path, pluginJsonEntry.FullName.Split('/')[0], plugin.IcoPath); + plugin.IcoPath = "Images\\zipfolder.png"; } } } @@ -584,7 +696,11 @@ private void Install(UserPlugin plugin, string downloadedFilePath) try { PluginManager.InstallPlugin(plugin, downloadedFilePath); - File.Delete(downloadedFilePath); + if (downloadedFilePath.StartsWith(Path.GetTempPath())) + { + File.Delete(downloadedFilePath); + } + } catch (FileNotFoundException e) { From 2606faae17b4b8ddddb182df85752efb282a4ac6 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 16 May 2024 21:22:08 +1000 Subject: [PATCH 3/4] minor fixes & refactor --- .../ExternalPlugins/UserPlugin.cs | 4 +- Flow.Launcher.Core/Plugin/PluginManager.cs | 11 +- .../SharedCommands/FilesFolders.cs | 19 ++ Flow.Launcher.Test/Plugins/ExplorerTest.cs | 24 +- .../Languages/en.xaml | 1 - .../PluginsManager.cs | 263 +++++------------- .../Utilities.cs | 30 +- 7 files changed, 149 insertions(+), 203 deletions(-) diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs index bb1279b2c61..5114c3ab4cc 100644 --- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs +++ b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Flow.Launcher.Core.ExternalPlugins { @@ -13,9 +13,11 @@ public record UserPlugin public string Website { get; set; } public string UrlDownload { get; set; } public string UrlSourceCode { get; set; } + public string LocalInstallPath { get; set; } public string IcoPath { get; set; } public DateTime LatestReleaseDate { get; set; } public DateTime DateAdded { get; set; } + public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 7f4d4ea35cf..e7dfb31c07a 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -380,7 +380,8 @@ public static bool PluginModified(string uuid) /// - /// Update a plugin to new version, from a zip file. Will Delete zip after updating. + /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, + /// unless it's a local path installation /// public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { @@ -390,11 +391,11 @@ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVe } /// - /// Install a plugin. Will Delete zip after updating. + /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation /// public static void InstallPlugin(UserPlugin plugin, string zipFilePath) { - InstallPlugin(plugin, zipFilePath, true); + InstallPlugin(plugin, zipFilePath, checkModified: true); } /// @@ -420,7 +421,9 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c // 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); + + if(!plugin.IsFromLocalInstallPath) + File.Delete(zipFilePath); var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 4137ca8d076..dd8c4b11232 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; #pragma warning disable IDE0005 using System.Windows; #pragma warning restore IDE0005 @@ -200,6 +201,24 @@ public static void OpenFile(string filePath, string workingDir = "", bool asAdmi } } + /// + /// This checks whether a given string is a zip file path. + /// By default does not check if the zip file actually exist on disk, can do so by + /// setting checkFileExists = true. + /// + public static bool IsZipFilePath(string querySearchString, bool checkFileExists = false) + { + if (IsLocationPathString(querySearchString) && querySearchString.Split('.').Last() == "zip") + { + if (checkFileExists) + return FileExists(querySearchString); + + return true; + } + + return false; + } + /// /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index e9d37433f4e..caa884b5145 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -186,16 +186,20 @@ public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileConte $"Actual result was: {result}{Environment.NewLine}"); } - [TestCase(@"c:\\", false)] - [TestCase(@"i:\", true)] - [TestCase(@"\c:\", false)] - [TestCase(@"cc:\", false)] - [TestCase(@"\\\SomeNetworkLocation\", false)] - [TestCase("RandomFile", false)] - [TestCase(@"c:\>*", true)] - [TestCase(@"c:\>", true)] - [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] - [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] + //[TestCase(@"c:\\", false)] + //[TestCase(@"i:\", true)] + //[TestCase(@"\c:\", false)] + //[TestCase(@"cc:\", false)] + //[TestCase(@"\\\SomeNetworkLocation\", false)] + //[TestCase(@"\\SomeNetworkLocation\", true)] + //[TestCase("RandomFile", false)] + //[TestCase(@"c:\>*", true)] + //[TestCase(@"c:\>", true)] + //[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] + //[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] + //[TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)] + [TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)] + public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index a89d9df213d..de6a3a2fb4c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -26,7 +26,6 @@ {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. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 69831d4398b..15cbda7f211 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -3,19 +3,15 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Windows; -using System.IO.Compression; -using Newtonsoft.Json; namespace Flow.Launcher.Plugin.PluginsManager { @@ -101,21 +97,12 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin) if (Context.API.GetAllPlugins() .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) { - if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - if (File.Exists(plugin.UrlDownload)) - { - Context.API.ChangeQuery( - $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.UrlDownload}"); - } - else - { - Context.API.ChangeQuery( - $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}"); - } - } + var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath; + + Context + .API + .ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}"); var mainWindow = Application.Current.MainWindow; mainWindow.Show(); @@ -148,12 +135,6 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin) MessageBoxButton.YesNo) == MessageBoxResult.No) return; - if (File.Exists(plugin.UrlDownload)) - { - Install(plugin, plugin.UrlDownload); - return; - } - // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download var downloadFilename = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}.zip" @@ -163,12 +144,17 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin) try { - if (File.Exists(filePath)) + if (!plugin.IsFromLocalInstallPath) { - File.Delete(filePath); - } + if (File.Exists(filePath)) + File.Delete(filePath); - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + } + else + { + filePath = plugin.LocalInstallPath; + } Install(plugin, filePath); } @@ -209,128 +195,38 @@ internal async ValueTask> RequestUpdateAsync(string search, Cancell { await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); - if (File.Exists(search) && search.Split('.').Last() == zip) - { - var plugin = null as UserPlugin; - - using (ZipArchive archive = ZipFile.OpenRead(search)) - { - var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); - ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); - - if (pluginJsonEntry != null) - { - using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) - { - string pluginJsonContent = await reader.ReadToEndAsync(); - plugin = JsonConvert.DeserializeObject(pluginJsonContent); - plugin.IcoPath = "Images\\zipfolder.png"; - } - } - } - if (plugin == null) - { - return new List - { - new Result - { - Title = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_title"), - SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_noresult_subtitle"), - IcoPath = icoPath - } - }; - } - - var pluginOld = Context.API.GetAllPlugins().FirstOrDefault(x => x.Metadata.ID == plugin.ID); - - return new List { - new Result - { - - Title = $"{plugin.Name} by {plugin.Author}", - SubTitle = $"Update from version {pluginOld.Metadata.Version} to {plugin.Version}", - IcoPath = pluginOld.Metadata.IcoPath, - Action = e => - { - string message; - if (Settings.AutoRestartAfterChanging) - { - message = string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), - plugin.Name, plugin.Author, - Environment.NewLine, Environment.NewLine); - } - else - { - message = string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"), - plugin.Name, plugin.Author, - Environment.NewLine); - } - - if (MessageBox.Show(message, - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) != MessageBoxResult.Yes) - { - return false; - } - - var downloadToFilePath = search; - - - PluginManager.UpdatePlugin(pluginOld.Metadata, plugin, - downloadToFilePath); - - if (Settings.AutoRestartAfterChanging) - { - Context.API.ShowMsg( - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - string.Format( - Context.API.GetTranslation( - "plugin_pluginsmanager_update_success_restart"), - plugin.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"), - plugin.Name)); - } + var pluginFromLocalPath = null as UserPlugin; + var updateFromLocalPath = false; - return true; + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + { + pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search); + pluginFromLocalPath.LocalInstallPath = search; + updateFromLocalPath = true; + } - }, - ContextData = - new UserPlugin - { - Website = plugin.Website, - UrlSourceCode = plugin.UrlSourceCode - } - }}; - } + var updateSource = !updateFromLocalPath + ? PluginsManifest.UserPlugins + : new List { pluginFromLocalPath }; var resultsForUpdate = ( from existingPlugin in Context.API.GetAllPlugins() - join pluginFromManifest in PluginsManifest.UserPlugins - on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version, + join pluginUpdateSource in updateSource + on existingPlugin.Metadata.ID equals pluginUpdateSource.ID + where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, StringComparison.InvariantCulture) < - 0 // if current version precedes manifest version + 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) && !PluginManager.PluginModified(existingPlugin.Metadata.ID) select new { - pluginFromManifest.Name, - pluginFromManifest.Author, + pluginUpdateSource.Name, + pluginUpdateSource.Author, CurrentVersion = existingPlugin.Metadata.Version, - NewVersion = pluginFromManifest.Version, + NewVersion = pluginUpdateSource.Version, existingPlugin.Metadata.IcoPath, PluginExistingMetadata = existingPlugin.Metadata, - PluginNewUserPlugin = pluginFromManifest + PluginNewUserPlugin = pluginUpdateSource }).ToList(); if (!resultsForUpdate.Any()) @@ -381,13 +277,21 @@ where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version _ = Task.Run(async delegate { - if (File.Exists(downloadToFilePath)) + if (!x.PluginNewUserPlugin.IsFromLocalInstallPath) { - File.Delete(downloadToFilePath); - } + if (File.Exists(downloadToFilePath)) + { + File.Delete(downloadToFilePath); + } - await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) - .ConfigureAwait(false); + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) + .ConfigureAwait(false); + } + else + { + downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath; + } + PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); @@ -516,7 +420,7 @@ await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFileP results = results.Prepend(updateAllResult); } - return Search(results, search); + return !updateFromLocalPath ? Search(results, search) : results.ToList(); } internal bool PluginExists(string id) @@ -590,52 +494,40 @@ internal List InstallFromWeb(string url) return new List { result }; } - internal List InstallFromLocal(string path) + internal List InstallFromLocalPath(string localPath) { - if (string.IsNullOrEmpty(path) || !File.Exists(path)) - { - return new List(); - } + var plugin = Utilities.GetPluginInfoFromZip(localPath); - var plugin = null as UserPlugin; + plugin.LocalInstallPath = localPath; - using (ZipArchive archive = ZipFile.OpenRead(path)) + return new List { - var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); - ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); - - if (pluginJsonEntry != null) + new Result { - using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + Title = $"{plugin.Name} by {plugin.Author}", + SubTitle = plugin.Description, + IcoPath = plugin.IcoPath, + Action = e => { - string pluginJsonContent = reader.ReadToEnd(); - plugin = JsonConvert.DeserializeObject(pluginJsonContent); - plugin.IcoPath = "Images\\zipfolder.png"; - } - } - } - - if (plugin == null) - { - return new List(); - } - - plugin.UrlDownload = path; + if (Settings.WarnFromUnknownSource) + { + if (!InstallSourceKnown(plugin.Website) + && MessageBox.Show(string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"), + Environment.NewLine), + Context.API.GetTranslation( + "plugin_pluginsmanager_install_unknown_source_warning_title"), + MessageBoxButton.YesNo) == MessageBoxResult.No) + return false; + } - var result = new Result - { - Title = plugin.Name, - SubTitle = plugin.UrlDownload, - IcoPath = plugin.IcoPath, - Action = e => - { - Application.Current.MainWindow.Hide(); - _ = InstallOrUpdateAsync(plugin); + Application.Current.MainWindow.Hide(); + _ = InstallOrUpdateAsync(plugin); - return ShouldHideWindow; + return ShouldHideWindow; + } } }; - return new List { result }; } private bool InstallSourceKnown(string url) @@ -657,8 +549,8 @@ internal async ValueTask> RequestInstallOrUpdate(string search, Can && search.Split('.').Last() == zip) return InstallFromWeb(search); - if (File.Exists(search) && search.Split('.').Last() == zip) - return InstallFromLocal(search); + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + return InstallFromLocalPath(search); var results = PluginsManifest @@ -693,14 +585,13 @@ private void Install(UserPlugin plugin, string downloadedFilePath) if (!File.Exists(downloadedFilePath)) throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath); + try { PluginManager.InstallPlugin(plugin, downloadedFilePath); - if (downloadedFilePath.StartsWith(Path.GetTempPath())) - { + + if (!plugin.IsFromLocalInstallPath) File.Delete(downloadedFilePath); - } - } catch (FileNotFoundException e) { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs index 792891ad1a5..9800fa0209c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs @@ -1,5 +1,10 @@ -using ICSharpCode.SharpZipLib.Zip; +using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Infrastructure.UserSettings; +using ICSharpCode.SharpZipLib.Zip; +using Newtonsoft.Json; using System.IO; +using System.IO.Compression; +using System.Linq; namespace Flow.Launcher.Plugin.PluginsManager { @@ -55,5 +60,28 @@ internal static string GetContainingFolderPathAfterUnzip(string unzippedParentFo return string.Empty; } + + internal static UserPlugin GetPluginInfoFromZip(string filePath) + { + var plugin = null as UserPlugin; + + using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath)) + { + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); + ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); + + if (pluginJsonEntry != null) + { + using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + { + string pluginJsonContent = reader.ReadToEnd(); + plugin = JsonConvert.DeserializeObject(pluginJsonContent); + plugin.IcoPath = "Images\\zipfolder.png"; + } + } + } + + return plugin; + } } } From 986ec3691e4eb56270208fe7c5d72f6ac6ab692f Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 16 May 2024 21:24:20 +1000 Subject: [PATCH 4/4] fix typo --- Flow.Launcher.Test/Plugins/ExplorerTest.cs | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index caa884b5145..80cb74729fc 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -186,18 +186,18 @@ public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileConte $"Actual result was: {result}{Environment.NewLine}"); } - //[TestCase(@"c:\\", false)] - //[TestCase(@"i:\", true)] - //[TestCase(@"\c:\", false)] - //[TestCase(@"cc:\", false)] - //[TestCase(@"\\\SomeNetworkLocation\", false)] - //[TestCase(@"\\SomeNetworkLocation\", true)] - //[TestCase("RandomFile", false)] - //[TestCase(@"c:\>*", true)] - //[TestCase(@"c:\>", true)] - //[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] - //[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] - //[TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)] + [TestCase(@"c:\\", false)] + [TestCase(@"i:\", true)] + [TestCase(@"\c:\", false)] + [TestCase(@"cc:\", false)] + [TestCase(@"\\\SomeNetworkLocation\", false)] + [TestCase(@"\\SomeNetworkLocation\", true)] + [TestCase("RandomFile", false)] + [TestCase(@"c:\>*", true)] + [TestCase(@"c:\>", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)] [TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)] public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)