Skip to content

Commit 4efac50

Browse files
authored
Merge pull request #2369 from VictoriousRaptor/delay-restart-2
Delay restart after installing/uninstalling/updating plugins
2 parents 57541b3 + c18ae41 commit 4efac50

File tree

8 files changed

+327
-135
lines changed

8 files changed

+327
-135
lines changed

Flow.Launcher.Core/Plugin/PluginManager.cs

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using Flow.Launcher.Infrastructure.UserSettings;
1212
using Flow.Launcher.Plugin;
1313
using ISavable = Flow.Launcher.Plugin.ISavable;
14+
using Flow.Launcher.Plugin.SharedCommands;
15+
using System.Text.Json;
1416

1517
namespace Flow.Launcher.Core.Plugin
1618
{
@@ -27,9 +29,9 @@ public static class PluginManager
2729

2830
public static IPublicAPI API { private set; get; }
2931

30-
// todo happlebao, this should not be public, the indicator function should be embeded
31-
public static PluginsSettings Settings;
32+
private static PluginsSettings Settings;
3233
private static List<PluginMetadata> _metadatas;
34+
private static List<string> _modifiedPlugins = new List<string>();
3335

3436
/// <summary>
3537
/// Directories that will hold Flow Launcher plugin directory
@@ -331,5 +333,156 @@ public static void ReplaceActionKeyword(string id, string oldActionKeyword, stri
331333
RemoveActionKeyword(id, oldActionKeyword);
332334
}
333335
}
336+
337+
private static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath)
338+
{
339+
var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length;
340+
var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length;
341+
342+
// adjust path depending on how the plugin is zipped up
343+
// the recommended should be to zip up the folder not the contents
344+
if (unzippedFolderCount == 1 && unzippedFilesCount == 0)
345+
// folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/
346+
return Directory.GetDirectories(unzippedParentFolderPath)[0];
347+
348+
if (unzippedFilesCount > 1)
349+
// content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/
350+
return unzippedParentFolderPath;
351+
352+
return string.Empty;
353+
}
354+
355+
private static bool SameOrLesserPluginVersionExists(string metadataPath)
356+
{
357+
var newMetadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(metadataPath));
358+
return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID
359+
&& newMetadata.Version.CompareTo(x.Metadata.Version) <= 0);
360+
}
361+
362+
#region Public functions
363+
364+
public static bool PluginModified(string uuid)
365+
{
366+
return _modifiedPlugins.Contains(uuid);
367+
}
368+
369+
370+
/// <summary>
371+
/// Update a plugin to new version, from a zip file. Will Delete zip after updating.
372+
/// </summary>
373+
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
374+
{
375+
InstallPlugin(newVersion, zipFilePath, checkModified:false);
376+
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
377+
_modifiedPlugins.Add(existingVersion.ID);
378+
}
379+
380+
/// <summary>
381+
/// Install a plugin. Will Delete zip after updating.
382+
/// </summary>
383+
public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
384+
{
385+
InstallPlugin(plugin, zipFilePath, true);
386+
}
387+
388+
/// <summary>
389+
/// Uninstall a plugin.
390+
/// </summary>
391+
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
392+
{
393+
UninstallPlugin(plugin, removeSettings, true);
394+
}
395+
396+
#endregion
397+
398+
#region Internal functions
399+
400+
internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified)
401+
{
402+
if (checkModified && PluginModified(plugin.ID))
403+
{
404+
// Distinguish exception from installing same or less version
405+
throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin));
406+
}
407+
408+
// Unzip plugin files to temp folder
409+
var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
410+
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath);
411+
File.Delete(zipFilePath);
412+
413+
var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath);
414+
415+
var metadataJsonFilePath = string.Empty;
416+
if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName)))
417+
metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName);
418+
419+
if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath))
420+
{
421+
throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist");
422+
}
423+
424+
if (SameOrLesserPluginVersionExists(metadataJsonFilePath))
425+
{
426+
throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}");
427+
}
428+
429+
var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}";
430+
431+
var defaultPluginIDs = new List<string>
432+
{
433+
"0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark
434+
"CEA0FDFC6D3B4085823D60DC76F28855", // Calculator
435+
"572be03c74c642baae319fc283e561a8", // Explorer
436+
"6A122269676E40EB86EB543B945932B9", // PluginIndicator
437+
"9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager
438+
"b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller
439+
"791FC278BA414111B8D1886DFE447410", // Program
440+
"D409510CD0D2481F853690A07E6DC426", // Shell
441+
"CEA08895D2544B019B2E9C5009600DF4", // Sys
442+
"0308FD86DE0A4DEE8D62B9B535370992", // URL
443+
"565B73353DBF4806919830B9202EE3BF", // WebSearch
444+
"5043CETYU6A748679OPA02D27D99677A" // WindowsSettings
445+
};
446+
447+
// Treat default plugin differently, it needs to be removable along with each flow release
448+
var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID)
449+
? DataLocation.PluginsDirectory
450+
: Constant.PreinstalledDirectory;
451+
452+
var newPluginPath = Path.Combine(installDirectory, folderName);
453+
454+
FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
455+
456+
Directory.Delete(tempFolderPluginPath, true);
457+
458+
if (checkModified)
459+
{
460+
_modifiedPlugins.Add(plugin.ID);
461+
}
462+
}
463+
464+
internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified)
465+
{
466+
if (checkModified && PluginModified(plugin.ID))
467+
{
468+
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
469+
}
470+
471+
if (removeSettings)
472+
{
473+
Settings.Plugins.Remove(plugin.ID);
474+
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
475+
}
476+
477+
// Marked for deletion. Will be deleted on next start up
478+
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
479+
480+
if (checkModified)
481+
{
482+
_modifiedPlugins.Add(plugin.ID);
483+
}
484+
}
485+
486+
#endregion
334487
}
335488
}

Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public ContextMenu(PluginInitContext context)
1313
Context = context;
1414
}
1515

16+
private readonly GlyphInfo sourcecodeGlyph = new("/Resources/#Segoe Fluent Icons","\uE943");
17+
private readonly GlyphInfo issueGlyph = new("/Resources/#Segoe Fluent Icons", "\ued15");
18+
private readonly GlyphInfo manifestGlyph = new("/Resources/#Segoe Fluent Icons", "\uea37");
19+
1620
public List<Result> LoadContextMenus(Result selectedResult)
1721
{
1822
if(selectedResult.ContextData is not UserPlugin pluginManifestInfo)
@@ -36,6 +40,7 @@ public List<Result> LoadContextMenus(Result selectedResult)
3640
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"),
3741
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"),
3842
IcoPath = "Images\\sourcecode.png",
43+
Glyph = sourcecodeGlyph,
3944
Action = _ =>
4045
{
4146
Context.API.OpenUrl(pluginManifestInfo.UrlSourceCode);
@@ -47,6 +52,7 @@ public List<Result> LoadContextMenus(Result selectedResult)
4752
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"),
4853
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"),
4954
IcoPath = "Images\\request.png",
55+
Glyph = issueGlyph,
5056
Action = _ =>
5157
{
5258
// standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master
@@ -63,6 +69,7 @@ public List<Result> LoadContextMenus(Result selectedResult)
6369
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"),
6470
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"),
6571
IcoPath = "Images\\manifestsite.png",
72+
Glyph = manifestGlyph,
6673
Action = _ =>
6774
{
6875
Context.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest");

Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,4 @@
3636
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3737
</Content>
3838
</ItemGroup>
39-
40-
<ItemGroup>
41-
<PackageReference Include="SharpZipLib" Version="1.4.2" />
42-
</ItemGroup>
4339
</Project>

Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
<system:String x:Key="plugin_pluginsmanager_download_success">Successfully downloaded {0}</system:String>
99
<system:String x:Key="plugin_pluginsmanager_download_error">Error: Unable to download the plugin</system:String>
1010
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt">{0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart.</system:String>
11+
<system:String x:Key="plugin_pluginsmanager_uninstall_prompt_no_restart">{0} by {1} {2}{2}Would you like to uninstall this plugin?</system:String>
1112
<system:String x:Key="plugin_pluginsmanager_install_prompt">{0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart.</system:String>
13+
<system:String x:Key="plugin_pluginsmanager_install_prompt_no_restart">{0} by {1} {2}{2}Would you like to install this plugin?</system:String>
1214
<system:String x:Key="plugin_pluginsmanager_install_title">Plugin Install</system:String>
1315
<system:String x:Key="plugin_pluginsmanager_installing_plugin">Installing Plugin</system:String>
1416
<system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String>
@@ -18,19 +20,25 @@
1820
<system:String x:Key="plugin_pluginsmanager_install_error_duplicate">Error: A plugin which has the same or greater version with {0} already exists.</system:String>
1921
<system:String x:Key="plugin_pluginsmanager_install_error_title">Error installing plugin</system:String>
2022
<system:String x:Key="plugin_pluginsmanager_install_error_subtitle">Error occurred while trying to install {0}</system:String>
23+
<system:String x:Key="plugin_pluginsmanager_uninstall_error_title">Error uninstalling plugin</system:String>
2124
<system:String x:Key="plugin_pluginsmanager_update_noresult_title">No update available</system:String>
2225
<system:String x:Key="plugin_pluginsmanager_update_noresult_subtitle">All plugins are up to date</system:String>
2326
<system:String x:Key="plugin_pluginsmanager_update_prompt">{0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart.</system:String>
27+
<system:String x:Key="plugin_pluginsmanager_update_prompt_no_restart">{0} by {1} {2}{2}Would you like to update this plugin?</system:String>
2428
<system:String x:Key="plugin_pluginsmanager_update_title">Plugin Update</system:String>
2529
<system:String x:Key="plugin_pluginsmanager_update_exists">This plugin has an update, would you like to see it?</system:String>
2630
<system:String x:Key="plugin_pluginsmanager_update_alreadyexists">This plugin is already installed</system:String>
2731
<system:String x:Key="plugin_pluginsmanager_update_failed_title">Plugin Manifest Download Failed</system:String>
2832
<system:String x:Key="plugin_pluginsmanager_update_failed_subtitle">Please check if you can connect to github.com. This error means you may not be able to install or update plugins.</system:String>
33+
<system:String x:Key="plugin_pluginsmanager_update_success_restart">Plugin {0} successfully updated. Restarting Flow, please wait...</system:String>
2934
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning_title">Installing from an unknown source</system:String>
3035
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning">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)</system:String>
31-
32-
<!-- Controls -->
33-
36+
37+
<system:String x:Key="plugin_pluginsmanager_install_success_no_restart">Plugin {0} successfully installed. Please restart Flow.</system:String>
38+
<system:String x:Key="plugin_pluginsmanager_uninstall_success_no_restart">Plugin {0} successfully uninstalled. Please restart Flow.</system:String>
39+
<system:String x:Key="plugin_pluginsmanager_update_success_no_restart">Plugin {0} successfully updated. Please restart Flow.</system:String>
40+
<system:String x:Key="plugin_pluginsmanager_plugin_modified_error">Plugin {0} has already been modified. Please restart Flow before making any further changes.</system:String>
41+
3442
<!-- Plugin Infos -->
3543
<system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String>
3644
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>
@@ -48,4 +56,5 @@
4856

4957
<!-- Settings menu items -->
5058
<system:String x:Key="plugin_pluginsmanager_plugin_settings_unknown_source">Install from unknown source warning</system:String>
59+
<system:String x:Key="plugin_pluginsmanager_plugin_settings_auto_restart">Automatically restart Flow Launcher after installing/uninstalling/updating plugins</system:String>
5160
</ResourceDictionary>

0 commit comments

Comments
 (0)