add a simple plugin system

This commit is contained in:
2023-11-14 14:20:26 +01:00
parent 1c4d7554e2
commit 9b7eb40cfc
20 changed files with 563 additions and 6 deletions

View File

@@ -0,0 +1,62 @@
using SM64RomManager.Functions;
using System.Drawing;
using System.Reflection;
using Telerik.WinControls;
using Telerik.WinControls.UI;
namespace Pilz.Plugins.Advanced
{
public static class Extensions
{
public static Icon? ToIcon(this Image image)
{
if (image is Bitmap bitmap)
return Icon.FromHandle(bitmap.GetHicon());
return null;
}
public static RadMenuItem GetAsItem(this PluginModule module, bool addDefaultHandler = true)
{
var item = new RadMenuItem
{
Text = module.Name,
SvgImage = module.Icon,
Tag = module,
Visibility = module.Visible ? ElementVisibility.Visible : ElementVisibility.Collapsed
};
if (addDefaultHandler)
item.Click += RadMenuItem_RMMethod_Click;
return item;
}
public static RadMenuItem GetAsItem(this PluginFunction function, bool addDefaultHandler = true)
{
var item = new RadMenuItem
{
Text = function.Name,
SvgImage = function.Icon,
Tag = function,
Visibility = function.Enabled ? ElementVisibility.Visible : ElementVisibility.Collapsed
};
if (addDefaultHandler)
item.Click += RadMenuItem_RMFunction_Click;
return item;
}
private static void RadMenuItem_RMMethod_Click(object? sender, EventArgs e)
{
if (sender is RadMenuItem item && item.Tag is PluginModule function)
function.ShowUI();
}
private static void RadMenuItem_RMFunction_Click(object? sender, EventArgs e)
{
if (sender is RadMenuItem item && item.Tag is PluginFunction function)
function.Execute();
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="UI.for.WinForms.AllControls.Net60" Version="2023.2.718" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pilz.Plugins\Pilz.Plugins.csproj" />
<ProjectReference Include="..\Pilz.UI.Telerik.SymbolFactory\Pilz.UI.Telerik.SymbolFactory.csproj" />
<ProjectReference Include="..\Pilz.UI.Telerik\Pilz.UI.Telerik.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telerik.WinControls;
namespace Pilz.Plugins.Advanced
{
public abstract class PluginFunction
{
public string Type { get; init; }
public string? Name { get; init; }
public RadSvgImage? Icon { get; set; }
public bool Enabled { get; set; } = true;
protected PluginFunction(string functionType)
{
Type = functionType;
}
protected PluginFunction(string functionType, string functionName) : this(functionType)
{
Name = functionName;
}
public object? Execute()
{
return Execute((PluginFunctionParameter?)null);
}
public T? Execute<T>(params object?[]? @params)
{
return Execute<T>(new PluginFunctionSimpleParamter(@params));
}
public object? Execute(params object?[]? @params)
{
return Execute(new PluginFunctionSimpleParamter(@params));
}
public T? Execute<T>(PluginFunctionSimpleParamter? @params)
{
if (Execute(@params) is T result)
return result;
return default;
}
public object? Execute(PluginFunctionParameter? @params)
{
#if !DEBUG
try
{
#endif
return ExecuteFunction(@params);
#if !DEBUG
}
catch (Exception)
{
return null;
}
#endif
}
protected abstract object? ExecuteFunction(PluginFunctionParameter? @params);
}
}

View File

@@ -0,0 +1,60 @@
namespace Pilz.Plugins.Advanced
{
public sealed class PluginFunctionController
{
public static PluginFunctionController Instance { get; private set; } = new();
private readonly List<PluginFunction> functions = new();
public IReadOnlyCollection<PluginFunction> Functions => functions.AsReadOnly();
public void RegisterFunction(PluginFunction function)
{
if (!functions.Contains(function))
functions.Add(function);
}
public void UnregisterFunction(PluginFunction function)
{
functions.Remove(function);
}
public IEnumerable<PluginFunction> GetFunctions(string functionType)
{
return functions.Where(n => n.Type == functionType);
}
public void ExecuteAll(string functionType)
{
foreach (var function in GetFunctions(functionType))
function.Execute();
}
public void ExecuteAll(string functionType, params object?[]? @params)
{
foreach (var function in GetFunctions(functionType))
function.Execute(@params);
}
public void ExecuteAll(string functionType, PluginFunctionParameter @params)
{
foreach (var function in GetFunctions(functionType))
function.Execute(@params);
}
public IEnumerable<object?> ExcuteAndGetResults(string functionType)
{
return GetFunctions(functionType).Select(n => n.Execute());
}
public IEnumerable<object?> ExcuteAndGetResults(string functionType, params object?[]? @params)
{
return GetFunctions(functionType).Select(n => n.Execute(@params));
}
public IEnumerable<object?> ExcuteAndGetResults(string functionType, PluginFunctionParameter @params)
{
return GetFunctions(functionType).Select(n => n.Execute(@params));
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins.Advanced
{
public class PluginFunctionParameter
{
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins.Advanced
{
public sealed class PluginFunctionSimpleParamter : PluginFunctionParameter
{
public object?[]? Params { get; init; }
public PluginFunctionSimpleParamter(params object?[]? @params)
{
Params = @params;
}
}
}

View File

@@ -0,0 +1,40 @@
using Pilz.UI.Telerik;
using Pilz.UI.Telerik.Dialogs;
using System.Drawing;
using System.Windows.Forms;
using Telerik.WinControls;
namespace Pilz.Plugins.Advanced
{
public abstract class PluginModule
{
public string Type { get; init; }
public string Name { get; init; }
public RadSvgImage? Icon { get; set; }
public bool Visible { get; set; } = true;
public bool RequiresRomManager { get; set; } = true;
public bool AllowEmbedding { get; set; } = true;
protected PluginModule(string moduleType, string moduleName)
{
Type = moduleType;
Name = moduleName;
}
public virtual void ShowUI()
{
if (CreateNewUI() is PluginModuleUI ui)
{
ui.BackColor = Color.Transparent;
DialogBaseForm.Show(ui, Name, Icon!.ToImage().ToIcon()!);
}
}
public PluginModuleUI CreateUI()
{
return CreateNewUI();
}
protected abstract PluginModuleUI CreateNewUI();
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Z.Collections.Extensions;
namespace Pilz.Plugins.Advanced
{
public sealed class PluginModuleController
{
public static PluginModuleController Instance { get; private set; } = new();
private readonly List<PluginModule> modules = new();
public IReadOnlyList<PluginModule> Modules => modules.AsReadOnly();
public void RegisterModule(PluginModule module)
{
if (!modules.Contains(module))
modules.Add(module);
}
public void UnregisterModule(PluginModule module)
{
modules.Remove(module);
}
}
}

View File

@@ -0,0 +1,22 @@
using Pilz.UI.Telerik.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pilz.Plugins.Advanced
{
public class PluginModuleUI : FlyoutDialogBase, ILoadContent
{
public PluginModuleUI()
{
ActionPanelVisible = false;
}
public virtual void LoadContent()
{
}
}
}

7
Pilz.Plugins/IPlugin.cs Normal file
View File

@@ -0,0 +1,7 @@
namespace Pilz.Plugins
{
public interface IPlugin
{
public string Name { get; }
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins
{
internal class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly? Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
return LoadFromAssemblyPath(assemblyPath);
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
return LoadUnmanagedDllFromPath(libraryPath);
return IntPtr.Zero;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins
{
public class PluginManager : PluginManager<IPlugin, PluginRuntimeInfo>
{
public static PluginManager Instance { get; private set; } = new();
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins
{
public class PluginManager<TPluginInterface, TPluginRuntimeInfo> where TPluginInterface : class where TPluginRuntimeInfo : PluginRuntimeInfo<TPluginInterface>
{
public static IEnumerable<TPluginRuntimeInfo> LoadPlugins(string[] paths)
{
var states = new List<TPluginRuntimeInfo>();
foreach (string path in paths)
states.Add(LoadPlugin(path));
return states;
}
public static TPluginRuntimeInfo LoadPlugin(string path)
{
var info = Activator.CreateInstance<TPluginRuntimeInfo>();
var irmplugin = typeof(TPluginInterface);
var loadContext = new PluginLoadContext(path);
Assembly? assembly = null;
if (File.Exists(path))
{
try
{
assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path)));
}
catch
{
info.Status = PluginStatus.ErrorAtLoading;
}
}
else
info.Status = PluginStatus.FileNotFound;
if (assembly != null)
{
foreach (var type in assembly.GetTypes())
{
if (info.Plugin == null && irmplugin.IsAssignableFrom(type))
{
try
{
if (Activator.CreateInstance(type) is TPluginInterface plugin)
{
info.Plugin = plugin;
info.Status = PluginStatus.Success;
}
}
catch
{
info.Status = PluginStatus.ErrorAtInitializing;
}
}
}
if (info.Plugin == null)
info.Status = PluginStatus.NoValidPlugin;
}
return info;
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins
{
public class PluginRuntimeInfo : PluginRuntimeInfo<IPlugin>
{
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins
{
public class PluginRuntimeInfo<T> where T : class
{
public T? Plugin { get; internal set; }
public PluginStatus Status { get; internal set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pilz.Plugins
{
public enum PluginStatus
{
None,
Success,
FileNotFound,
ErrorAtLoading,
ErrorAtInitializing,
NoValidPlugin
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telerik.WinControls;
using Telerik.WinControls.Svg;
namespace Pilz.UI.Telerik
{
public static class Extensions
{
public static Image ToImage(this RadSvgImage svg)
{
return svg?.Document.Draw(svg.Width, svg.Height);
}
public static void ApplyColor(this RadSvgImage svg, Color color)
{
svg.Document.Fill = new SvgColourServer(color);
svg.ClearCache();
}
}
}

View File

@@ -52,10 +52,7 @@ namespace Pilz.UI.Telerik
public virtual RadSvgImage GetSvgImageColored(TSvgSymbols svgImage, Size size, Color color)
{
var img = GetSvgImage(svgImage, size);
img.Document.Fill = new SvgColourServer(color);
img.ClearCache();
img.ApplyColor(color);
return img;
}
@@ -81,7 +78,7 @@ namespace Pilz.UI.Telerik
public virtual Image GetImageFromSvg(RadSvgImage svg)
{
return svg.Document.Draw(svg.Width, svg.Height);
return svg.ToImage();
}
}
}

View File

@@ -35,7 +35,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.UI.Telerik.SymbolFacto
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.UI.Telerik", "Pilz.UI.Telerik\Pilz.UI.Telerik.csproj", "{DF674119-CC28-40AA-968F-1E23D184A491}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Networking.CloudProviders.Nextcloud", "Pilz.Networking.CloudProviders.Nextcloud\Pilz.Networking.CloudProviders.Nextcloud.csproj", "{A91E966B-3A82-4F32-A703-2FC1C7654FD1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Networking.CloudProviders.Nextcloud", "Pilz.Networking.CloudProviders.Nextcloud\Pilz.Networking.CloudProviders.Nextcloud.csproj", "{A91E966B-3A82-4F32-A703-2FC1C7654FD1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Plugins", "Pilz.Plugins\Pilz.Plugins.csproj", "{1170FCA6-192D-42FE-A79F-49EE03035554}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Plugins.Advanced", "Pilz.Plugins.Advanced\Pilz.Plugins.Advanced.csproj", "{72153EC8-B297-4A94-80AA-3574544BE8CF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -181,6 +185,22 @@ Global
{A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|Any CPU.Build.0 = Release|Any CPU
{A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|x86.ActiveCfg = Release|Any CPU
{A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|x86.Build.0 = Release|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|x86.ActiveCfg = Debug|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|x86.Build.0 = Debug|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Release|Any CPU.Build.0 = Release|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Release|x86.ActiveCfg = Release|Any CPU
{1170FCA6-192D-42FE-A79F-49EE03035554}.Release|x86.Build.0 = Release|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|x86.ActiveCfg = Debug|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|x86.Build.0 = Debug|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|Any CPU.Build.0 = Release|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|x86.ActiveCfg = Release|Any CPU
{72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE