Files
Pilz/Pilz.Plugins/PluginManager{T,T}.cs
2025-10-20 05:57:17 +02:00

218 lines
7.9 KiB
C#

using Pilz.Text;
using System.Reflection;
namespace Pilz.Plugins;
public class PluginManager<TPluginInterface, TPluginRuntimeInfo> where TPluginInterface : class where TPluginRuntimeInfo : PluginRuntimeInfo
{
protected readonly List<TPluginRuntimeInfo> loadedPlugins = [];
/// <summary>
/// The default initialization parameters used when no parameters passed. Commonly used for dynamically loading assemblies.
/// </summary>
public virtual object?[] DefaultParameters { get; set; } = [];
/// <summary>
/// Returns a list of all currently loaded plugins.
/// </summary>
public virtual IEnumerable<TPluginRuntimeInfo> LoadedPlugins => loadedPlugins.AsReadOnly();
protected void OnCurrentAppDomainAssemblyLoad(object? sender, AssemblyLoadEventArgs args)
{
LoadPlugins(args.LoadedAssembly, null);
}
/// <summary>
/// Loads assembly from the given file paths and then loads the plugins from the assemblies.
/// </summary>
/// <param name="paths"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public virtual IEnumerable<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>> LoadPlugins(string[] paths, object?[]? parameters)
{
var results = new List<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>>();
foreach (var path in paths)
results.Add(LoadPlugin(path, parameters));
return results;
}
/// <summary>
/// Loads plugins from the given assemblies.
/// </summary>
/// <param name="assemblies"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public virtual IEnumerable<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>> LoadPlugins(Assembly[] assemblies, object?[]? parameters)
{
var results = new List<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>>();
foreach (var assembly in assemblies)
results.Add(LoadPlugins(assembly, parameters));
return results;
}
/// <summary>
/// Loads plugins from already loaded assemblies for the current <see cref="AppDomain.CurrentDomain"/>.
/// </summary>
/// <returns></returns>
public virtual IEnumerable<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>> LoadOwnPlugins(object?[]? parameters, string? whitelist)
{
return LoadOwnPlugins(false, parameters, whitelist);
}
/// <summary>
/// Loads plugins from already loaded assemblies for the current <see cref="AppDomain.CurrentDomain"/>.
/// </summary>
/// <param name="listenAssemblyLoadContext">Do also load plugins from all yet not loaded assemblies by listening the event <see cref="AppDomain.AssemblyLoad"/></param>
/// <param name="whitelist"></param>>.
/// <param name="parameters"></param>>.
/// <returns></returns>
public virtual IEnumerable<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>> LoadOwnPlugins(bool listenAssemblyLoadContext, object?[]? parameters, string? whitelist)
{
var results = new List<PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>>();
if (listenAssemblyLoadContext)
AppDomain.CurrentDomain.AssemblyLoad += OnCurrentAppDomainAssemblyLoad;
var assemblies = AppDomain.CurrentDomain.GetAssemblies().AsEnumerable();
if (!string.IsNullOrWhiteSpace(whitelist))
assemblies = assemblies.Where(n => LikeOperator.IsLike(n.GetName().Name, whitelist));
foreach (var assembly in assemblies)
results.Add(LoadPlugins(assembly, parameters));
return results;
}
/// <summary>
/// Loads an assembly from the given file path and then loads plugins from the assembly.
/// </summary>
/// <param name="path"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public virtual PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo> LoadPlugin(string path, object?[]? parameters)
{
var result = new PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>();
var loadContext = new PluginLoadContext(path);
if (File.Exists(path))
{
try
{
result.Assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path)));
}
catch
{
result.Status = PluginLoadStatus.ErrorAtLoading;
}
}
else
result.Status = PluginLoadStatus.FileNotFound;
if (result.Assembly != null)
LoadPlugin(result, parameters);
return result;
}
/// <summary>
/// Load plugins from a given assembly.
/// </summary>
/// <param name="assembly"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public virtual PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo> LoadPlugins(Assembly assembly, object?[]? parameters)
{
var result = new PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>
{
Assembly = assembly
};
LoadPlugin(result, parameters);
return result;
}
/// <summary>
/// Loads the plugin from a given type.
/// <br/>Commonly used to ensure loading core plugins.
/// </summary>
/// <typeparam name="TPlugin"></typeparam>
/// <param name="parameters"></param>
/// <returns></returns>
public virtual PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo> LoadPlugin<TPlugin>(object?[]? parameters) where TPlugin : TPluginInterface
{
var result = new PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo>
{
Assembly = typeof(TPlugin).Assembly
};
LoadPlugin(result, typeof(TPlugin), parameters);
return result;
}
protected virtual void LoadPlugin(PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo> result, object?[]? parameters)
{
if (result.Assembly == null)
result.Status = PluginLoadStatus.NoValidPlugin;
//else if (loadedPlugins.Any(n => n.Assembly == result.Assembly))
// result.Status = PluginLoadStatus.AlreadyLoaded;
else
{
foreach (var type in result.Assembly.GetTypes())
LoadPlugin(result, type, parameters);
}
if (result.PluginsInternal.Count == 0)
result.Status = PluginLoadStatus.NoValidPlugin;
}
protected virtual void LoadPlugin(PluginLoadInfo<TPluginInterface, TPluginRuntimeInfo> result, Type type, object?[]? parameters)
{
if (loadedPlugins.Any(n => n.Plugin != null && n.Plugin.GetType() == type))
result.Status = PluginLoadStatus.AlreadyLoaded;
else if (type.IsAssignableTo(typeof(TPluginInterface)))
{
var info = Activator.CreateInstance<TPluginRuntimeInfo>();
info.Assembly = result.Assembly;
result.PluginsInternal.Add(info);
try
{
if (Activator.CreateInstance(type, GetConstructorFlags(), null, GetParameters(type, parameters), null) is TPluginInterface plugin)
{
if (plugin is IPluginLateInitialization pluginLateInit)
pluginLateInit.LateInit();
info.Plugin = plugin;
info.Status = PluginStatus.Success;
loadedPlugins.Add(info);
}
}
catch (Exception ex)
{
info.Exception = ex;
info.Status = PluginStatus.ErrorAtInitializing;
}
}
else
result.Status = PluginLoadStatus.NoValidPlugin;
}
protected virtual object?[]? GetParameters(Type type, object?[]? parameters)
{
if (parameters == null || parameters.Length == 0)
return DefaultParameters;
return parameters;
}
protected virtual BindingFlags GetConstructorFlags()
{
return BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance;
}
}