Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Plugin Metadata & Path Management #3272

Open
wants to merge 31 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
89bca3b
Add plugin cache path
Jack251970 Feb 23, 2025
5919418
Add assembly name & plugin settings path & plugin cache path in meta …
Jack251970 Feb 23, 2025
f1b5e68
Remove reflection codes for deleting csharp plugin settings
Jack251970 Feb 23, 2025
1aaba46
Use constants & data location for code quality
Jack251970 Feb 23, 2025
9cd3011
Add documents for plugin metadata
Jack251970 Feb 23, 2025
8f7ad27
Remove useless usings
Jack251970 Feb 23, 2025
d07b304
Improve code quality
Jack251970 Feb 24, 2025
b50db58
Add assembly name & plugin settings path & plugin cache path in meta …
Jack251970 Feb 24, 2025
6012111
Add log directory & version log directory & themes directory in data …
Jack251970 Feb 24, 2025
3efe550
Use context plugin settings path
Jack251970 Feb 24, 2025
126153b
Improve plugin settings directory clean & Support plugin cache direct…
Jack251970 Feb 24, 2025
3106b02
Support plugin directory update & validate
Jack251970 Feb 24, 2025
012ef49
Fix log directory fetch issue
Jack251970 Feb 24, 2025
58de625
Do not validate plugin settings & cache path
Jack251970 Feb 24, 2025
47adfd1
Improve code quality
Jack251970 Feb 24, 2025
a0c2a42
Let Program plugin use plugin cache path
Jack251970 Feb 24, 2025
a29ed64
Use metadata for plugin settings directory
Jack251970 Feb 24, 2025
65ae342
Add documents for Flow.Launcher.Plugin
Jack251970 Feb 24, 2025
6622815
Fix typos
Jack251970 Feb 24, 2025
fe86e23
Add exception handles
Jack251970 Feb 24, 2025
f8d0981
Update json rpc plugin directory before loading plugins
Jack251970 Feb 25, 2025
7975ab5
Merge branch 'dev' into plugin_settings_cache_path
Jack251970 Mar 1, 2025
ce3a3e9
Fix plugin settings delete issue
Jack251970 Mar 6, 2025
486cc6a
Fix async task issue
Jack251970 Mar 6, 2025
af3b391
Fix dispose
Jack251970 Mar 6, 2025
c690e59
Merge branch 'dev' into plugin_settings_cache_path
Jack251970 Mar 16, 2025
b0b1a26
Fix build issue & Cleanup codes
Jack251970 Mar 16, 2025
e3af882
Merge branch 'dev' into plugin_settings_cache_path
Jack251970 Mar 20, 2025
c87b731
Merge branch 'dev' into plugin_settings_cache_path
Jack251970 Mar 20, 2025
ee0b039
Merge update plugin directory functions
Jack251970 Mar 23, 2025
5f976b9
Remove useless assignment
Jack251970 Mar 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
Expand Down Expand Up @@ -116,7 +117,10 @@ private IEnumerable<PluginPair> SetPathForPluginPairs(string filePath, string la
foreach (var metadata in PluginMetadataList)
{
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
{
metadata.AssemblyName = string.Empty;
pluginPairs.Add(CreatePluginPair(filePath, metadata));
}
}

return pluginPairs;
Expand Down
14 changes: 1 addition & 13 deletions Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Microsoft.IO;
using System.Windows;
using System.Windows.Controls;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using CheckBox = System.Windows.Controls.CheckBox;
using Control = System.Windows.Controls.Control;
using Orientation = System.Windows.Controls.Orientation;
using TextBox = System.Windows.Controls.TextBox;
using UserControl = System.Windows.Controls.UserControl;
using System.Windows.Documents;

namespace Flow.Launcher.Core.Plugin
{
Expand All @@ -42,7 +30,7 @@ internal abstract class JsonRPCPlugin : JsonRPCPluginBase
private int RequestId { get; set; }

private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
private string SettingPath => Path.Combine(Context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "Settings.json");

public override List<Result> LoadContextMenus(Result selectedResult)
{
Expand Down
28 changes: 1 addition & 27 deletions Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Infrastructure;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Microsoft.IO;
using System.Windows;
using System.Windows.Controls;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using CheckBox = System.Windows.Controls.CheckBox;
using Control = System.Windows.Controls.Control;
using Orientation = System.Windows.Controls.Orientation;
using TextBox = System.Windows.Controls.TextBox;
using UserControl = System.Windows.Controls.UserControl;
using System.Windows.Documents;
using static System.Windows.Forms.LinkLabel;
using Droplex;
using System.Windows.Forms;
using Microsoft.VisualStudio.Threading;

namespace Flow.Launcher.Core.Plugin
{
Expand All @@ -44,8 +27,7 @@ public abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISettingPr
private string SettingConfigurationPath =>
Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");

private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory,
Context.CurrentPluginMetadata.Name);
private string SettingDirectory => Context.CurrentPluginMetadata.PluginSettingsDirectoryPath;

private string SettingPath => Path.Combine(SettingDirectory, "Settings.json");

Expand Down Expand Up @@ -166,13 +148,5 @@ public Control CreateSettingPanel()
{
return Settings.CreateSettingPanel();
}

public void DeletePluginSettingsDirectory()
{
if (Directory.Exists(SettingDirectory))
{
Directory.Delete(SettingDirectory, true);
}
}
}
}
7 changes: 3 additions & 4 deletions Flow.Launcher.Core/Plugin/PluginConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
Expand All @@ -9,7 +9,6 @@

namespace Flow.Launcher.Core.Plugin
{

internal abstract class PluginConfig
{
/// <summary>
Expand Down Expand Up @@ -112,7 +111,7 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory)
metadata = JsonSerializer.Deserialize<PluginMetadata>(File.ReadAllText(configPath));
metadata.PluginDirectory = pluginDirectory;
// for plugins which doesn't has ActionKeywords key
metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword };
metadata.ActionKeywords ??= new List<string> { metadata.ActionKeyword };
// for plugin still use old ActionKeyword
metadata.ActionKeyword = metadata.ActionKeywords?[0];
}
Expand All @@ -137,4 +136,4 @@ private static PluginMetadata GetPluginMetadata(string pluginDirectory)
return metadata;
}
}
}
}
132 changes: 77 additions & 55 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static class PluginManager

private static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas;
private static List<string> _modifiedPlugins = new List<string>();
private static List<string> _modifiedPlugins = new();

/// <summary>
/// Directories that will hold Flow Launcher plugin directory
Expand Down Expand Up @@ -72,15 +72,20 @@ public static async ValueTask DisposePluginsAsync()
{
foreach (var pluginPair in AllPlugins)
{
switch (pluginPair.Plugin)
{
case IDisposable disposable:
disposable.Dispose();
break;
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync();
break;
}
await DisposePluginAsync(pluginPair);
}
}

private static async Task DisposePluginAsync(PluginPair pluginPair)
{
switch (pluginPair.Plugin)
{
case IDisposable disposable:
disposable.Dispose();
break;
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync();
break;
}
}

Expand Down Expand Up @@ -155,6 +160,25 @@ public static void LoadPlugins(PluginsSettings settings)
Settings = settings;
Settings.UpdatePluginSettings(_metadatas);
AllPlugins = PluginsLoader.Plugins(_metadatas, Settings);
// Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins
UpdatePluginDirectory(_metadatas);
}

private static void UpdatePluginDirectory(List<PluginMetadata> metadatas)
{
foreach (var metadata in metadatas)
{
if (AllowedLanguage.IsDotNet(metadata.Language))
{
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName);
metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName);
}
else
{
metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name);
metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name);
}
}
}

/// <summary>
Expand Down Expand Up @@ -228,10 +252,9 @@ public static ICollection<PluginPair> ValidPluginsForQuery(Query query)
if (query is null)
return Array.Empty<PluginPair>();

if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword))
if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code quality improvement

return GlobalPlugins;

var plugin = NonGlobalPlugins[query.ActionKeyword];
return new List<PluginPair>
{
plugin
Expand Down Expand Up @@ -445,10 +468,10 @@ public static bool PluginModified(string uuid)
/// 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
/// </summary>
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{
InstallPlugin(newVersion, zipFilePath, checkModified:false);
UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
await UninstallPluginAsync(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
_modifiedPlugins.Add(existingVersion.ID);
}

Expand All @@ -463,9 +486,9 @@ public static void InstallPlugin(UserPlugin plugin, string zipFilePath)
/// <summary>
/// Uninstall a plugin.
/// </summary>
public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
public static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
{
UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true);
await UninstallPluginAsync(plugin, removePluginFromSettings, removePluginSettings, true);
}

#endregion
Expand Down Expand Up @@ -546,63 +569,62 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c
}
}

internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
{
if (checkModified && PluginModified(plugin.ID))
{
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
}

if (removePluginSettings)
if (removePluginSettings || removePluginFromSettings)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between these two?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removePluginFromSettings: When one plugin is deleted, this flag will be true and we will remove it from Settings.Plugins and AllPlugins so that FL will not have this plugin. Additionally, we need to clean the cache folder of this plugin.

removePluginSettings: When one plugin is deleted, FL will ask users if they want to remove the settings of this plugin. Users can choose no and FL will keep its settings for next usage.

Because these two will all delete folders of plugins (one for cache folder, another for settings folder), so we need to dispose plugin if either of them is true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emmm I think the first one is pretty weird...but sure

{
if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader
// If we want to remove plugin from AllPlugins,
// we need to dispose them so that they can release file handles
// which can help FL to delete the plugin settings & cache folders successfully
var pluginPairs = AllPlugins.FindAll(p => p.Metadata.ID == plugin.ID);
foreach (var pluginPair in pluginPairs)
{
var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath);
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
var assemblyName = assembly.GetName().Name;
await DisposePluginAsync(pluginPair);
}
}

// if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin
// so we need to remove it from the api instance
if (removePluginSettings)
{
// For dotnet plugins, we need to remove their PluginJsonStorage instance
if (AllowedLanguage.IsDotNet(plugin.Language))
{
var method = API.GetType().GetMethod("RemovePluginSettings");
var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName });
method?.Invoke(API, new object[] { plugin.AssemblyName });
}

// if there exists a json storage for current plugin, we need to delete the directory path
if (pluginJsonStorage != null)
{
var deleteMethod = pluginJsonStorage.GetType().GetMethod("DeleteDirectory");
try
{
deleteMethod?.Invoke(pluginJsonStorage, null);
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}
try
{
var pluginSettingsDirectory = plugin.PluginSettingsDirectoryPath;
if (Directory.Exists(pluginSettingsDirectory))
Directory.Delete(pluginSettingsDirectory, true);
}
else // the plugin with json prc interface
catch (Exception e)
{
var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID);
if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin)
{
try
{
jsonRpcPlugin.DeletePluginSettingsDirectory();
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}

if (removePluginFromSettings)
{
try
{
var pluginCacheDirectory = plugin.PluginCacheDirectoryPath;
if (Directory.Exists(pluginCacheDirectory))
Directory.Delete(pluginCacheDirectory, true);
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin cache folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
}
Settings.Plugins.Remove(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
}
Expand Down
Loading
Loading