From 248b098e54d9e6cedde4e89f80869feeb0a623c6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 9 Jun 2025 20:28:18 +0800 Subject: [PATCH] Support installing from local path --- Flow.Launcher.Core/Plugin/PluginManager.cs | 55 +++++++++++++++++++ Flow.Launcher/Languages/en.xaml | 6 ++ .../SettingsPanePluginStoreViewModel.cs | 35 +++++++++++- .../Views/SettingsPanePluginStore.xaml | 7 +++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index ef831e940..f7a2461ab 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text.Json; using System.Threading; @@ -633,6 +634,42 @@ namespace Flow.Launcher.Core.Plugin } } + public static async Task InstallPluginAndCheckRestartAsync(string filePath) + { + UserPlugin plugin; + try + { + using ZipArchive archive = ZipFile.OpenRead(filePath); + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ?? + throw new FileNotFoundException("The zip file does not contain a plugin.json file."); + var pluginJsonEntry = archive.GetEntry(pluginJsonPath.ToString()) ?? + throw new FileNotFoundException("The zip file does not contain a plugin.json file."); + + using Stream stream = pluginJsonEntry.Open(); + plugin = JsonSerializer.Deserialize(stream); + plugin.IcoPath = "Images\\zipfolder.png"; + plugin.LocalInstallPath = filePath; + } + catch (Exception e) + { + API.LogException(ClassName, "Failed to validate zip file", e); + API.ShowMsgError(API.GetTranslation("ZipFileNotHavePluginJson")); + return; + } + + if (FlowSettings.ShowUnknownSourceWarning) + { + if (!InstallSourceKnown(plugin.Website) + && API.ShowMsgBox(string.Format( + API.GetTranslation("InstallFromUnknownSourceSubtitle"), Environment.NewLine), + API.GetTranslation("InstallFromUnknownSourceTitle"), + MessageBoxButton.YesNo) == MessageBoxResult.No) + return; + } + + await InstallPluginAndCheckRestartAsync(plugin); + } + public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin) { if (API.ShowMsgBox( @@ -913,6 +950,24 @@ namespace Flow.Launcher.Core.Plugin } } + private static bool InstallSourceKnown(string url) + { + var pieces = url.Split('/'); + + if (pieces.Length < 4) + return false; + + var author = pieces[3]; + var acceptedSource = "https://github.com"; + var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author); + + return url.StartsWith(acceptedSource) && + API.GetAllPlugins().Any(x => + !string.IsNullOrEmpty(x.Metadata.Website) && + x.Metadata.Website.StartsWith(constructedUrlPart) + ); + } + #endregion } } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index b3fdd6892..6ce5d17d0 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -204,6 +204,12 @@ {0} by {1} {2}{2}Would you like to update this plugin? Downloading plugin Automatically restart after installing/uninstalling/updating plugins in plugin store + Zip file does not have a valid plugin.json configuration + Installing from an unknown source + This plugin is from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning in general section of setting window) + Zip files + Please select zip file + Install plugin from local path Theme diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index 07df0682d..b9b7c12fa 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Windows.Forms; using CommunityToolkit.Mvvm.Input; +using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; @@ -96,6 +99,36 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel } } + [RelayCommand] + private async Task InstallPluginAsync() + { + var file = GetFileFromDialog( + App.API.GetTranslation("SelectZipFile"), + $"{App.API.GetTranslation("ZipFiles")} (*.zip)|*.zip"); + + if (!string.IsNullOrEmpty(file)) + await PluginManager.InstallPluginAndCheckRestartAsync(file); + } + + private static string GetFileFromDialog(string title, string filter = "") + { + var dlg = new OpenFileDialog + { + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Downloads", + Multiselect = false, + CheckFileExists = true, + CheckPathExists = true, + Title = title, + Filter = filter + }; + + return dlg.ShowDialog() switch + { + DialogResult.OK => dlg.FileName, + _ => string.Empty + }; + } + public bool SatisfiesFilter(PluginStoreItemViewModel plugin) { // Check plugin language diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml index 9312b0c2d..68f78d46c 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml @@ -92,6 +92,13 @@ +