/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Runtime
* File: LoaderExtensions.cs
*
* LoaderExtensions.cs is part of VNLib.Plugins.Runtime which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Runtime is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Plugins.Runtime is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Collections.Generic;
using VNLib.Utils.IO;
using VNLib.Utils.Extensions;
namespace VNLib.Plugins.Runtime
{
///
/// Contains extension methods for PluginLoader library
///
public static class LoaderExtensions
{
/*
* Class that manages a collection registration for a specific type
* dependency, and redirects the event calls for the consumed service
*/
private sealed class TypedRegistration : IPluginEventListener where T: class
{
private readonly ITypedPluginConsumer _consumerEvents;
private readonly object? _userState;
private T? _service;
private readonly Type _type;
public TypedRegistration(ITypedPluginConsumer consumerEvents, Type type)
{
_consumerEvents = consumerEvents;
_type = type;
}
public void OnPluginLoaded(PluginController controller, object? state)
{
//Get the service from the loaded plugins
T service = controller.Plugins
.Where(pl => _type.IsAssignableFrom(pl.PluginType))
.Select(static pl => (T)pl.Plugin!)
.First();
//Call load with the exported type
_consumerEvents.OnLoad(service, _userState);
//Store for unload
_service = service;
}
public void OnPluginUnloaded(PluginController controller, object? state)
{
//Unload
_consumerEvents.OnUnload(_service!, _userState);
_service = null;
}
}
///
/// Registers a plugin even handler for the current
/// for a specific type.
///
///
///
/// The typed plugin instance event consumer
/// A handle that manages this event registration
///
public static PluginEventRegistration RegisterForType(this PluginController collection, ITypedPluginConsumer consumer) where T: class
{
Type serviceType = typeof(T);
//Confim the type is exposed by this collection
if(!ExposesType(collection, serviceType))
{
throw new ArgumentException("The requested type is not exposed in this assembly");
}
//Create new typed listener
TypedRegistration reg = new(consumer, serviceType);
//register event handler
return Register(collection, reg, null);
}
///
/// Registers a handler to listen for plugin load/unload events
///
///
/// A handle that will unregister the listener when disposed
public static PluginEventRegistration Register(this IPluginEventRegistrar reg, IPluginEventListener listener, object? state = null)
{
reg.Register(listener, state);
return new(reg, listener);
}
///
/// Determines if the current
/// exposes the desired type on is
/// type.
///
///
/// The desired type to request
/// True if the plugin exposes the desired type, false otherwise
public static bool ExposesType(this PluginController collection, Type type)
{
return collection.Plugins
.Where(pl => type.IsAssignableFrom(pl.PluginType))
.Any();
}
///
/// Searches all plugins within the current loader for a
/// single plugin that derrives the specified type
///
/// The type the plugin must derrive from
///
/// The instance of your custom type casted, or null if not found or could not be casted
public static T? GetExposedTypes(this PluginController collection) where T: class
{
LivePlugin? plugin = collection.Plugins
.Where(static pl => typeof(T).IsAssignableFrom(pl.PluginType))
.SingleOrDefault();
return plugin?.Plugin as T;
}
///
/// Serially initialzies all plugin lifecycle controllers and configures
/// plugin instances.
///
///
///
public static void InitializeAll(this IPluginStack runtime)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
foreach(RuntimePluginLoader loader in runtime.Plugins)
{
loader.InitializeController();
}
}
///
/// Invokes the load method for all plugin instances
///
///
///
///
public static void InvokeLoad(this IPluginStack runtime)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
//try loading all plugins
runtime.Plugins.TryForeach(static p => p.LoadPlugins());
}
///
/// Invokes the unload method for all plugin instances
///
///
///
///
public static void InvokeUnload(this IPluginStack runtime)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
//try unloading all plugins
runtime.Plugins.TryForeach(static p => p.UnloadPlugins());
}
///
/// Unloads all plugins and the plugin assembly loader
/// if unloading is supported.
///
///
///
///
public static void UnloadAll(this IPluginStack runtime)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
//try unloading all plugins and their loaders
runtime.Plugins.TryForeach(static p => p.UnloadAll());
}
///
/// Reloads all plugins and each assembly loader
///
///
///
///
public static void ReloadAll(this IPluginStack runtime)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
//try reloading all plugins
runtime.Plugins.TryForeach(static p => p.ReloadPlugins());
}
///
/// Registers a plugin event listener for all plugins
///
///
/// The event listener instance
/// Optional state parameter
///
public static void RegsiterListener(this IPluginStack runtime, IPluginEventListener listener, object? state = null)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
_ = listener ?? throw new ArgumentNullException(nameof(listener));
//Register for all plugins
foreach (PluginController controller in runtime.Plugins.Select(static p => p.Controller))
{
controller.Register(listener, state);
}
}
///
/// Unregisters a plugin event listener for all plugins
///
///
/// The listener instance to unregister
///
public static void UnregsiterListener(this IPluginStack runtime, IPluginEventListener listener)
{
_ = runtime ?? throw new ArgumentNullException(nameof(runtime));
_ = listener ?? throw new ArgumentNullException(nameof(listener));
//Unregister for all plugins
foreach (PluginController controller in runtime.Plugins.Select(static p => p.Controller))
{
controller.Unregister(listener);
}
}
///
/// Specify the host configuration data to pass to the plugin
///
///
/// A configuration element to pass to the plugin's host config element
/// The current builder instance for chaining
public static PluginStackBuilder WithConfigurationData(this PluginStackBuilder builder, JsonElement hostConfig)
{
_ = builder ?? throw new ArgumentNullException(nameof(builder));
//Clone the host config into binary
using VnMemoryStream ms = new();
using (Utf8JsonWriter writer = new(ms))
{
hostConfig.WriteTo(writer);
}
//Store binary
return builder.WithConfigurationData(ms.AsSpan());
}
///
/// Specifies the directory that the plugin loader will search for plugins in
///
/// The search directory path
///
/// The current builder instance for chaining
///
public static PluginStackBuilder WithSearchDirectory(this PluginStackBuilder builder, string path) => WithSearchDirectory(builder, new DirectoryInfo(path));
///
/// Specifies the directory that the plugin loader will search for plugins in
///
/// The search directory instance
///
/// The current builder instance for chaining
///
public static PluginStackBuilder WithSearchDirectory(this PluginStackBuilder builder, DirectoryInfo dir)
{
_ = builder ?? throw new ArgumentNullException(nameof(builder));
_ = dir ?? throw new ArgumentNullException(nameof(dir));
PluginDirectorySearcher dirSearcher = new (dir);
builder.WithDiscoveryManager(dirSearcher);
return builder;
}
///
/// Gets the current collection of loaded plugins for the plugin stack
///
///
/// An enumeration of all wrappers
public static IEnumerable GetAllPlugins(this IPluginStack stack) => stack.Plugins.SelectMany(static p => p.Controller.Plugins);
private sealed record class PluginDirectorySearcher(DirectoryInfo Dir) : IPluginDiscoveryManager
{
private const string PLUGIN_FILE_EXTENSION = ".dll";
///
public string[] DiscoverPluginFiles()
{
//Enumerate all dll files within the seach directory
IEnumerable dirs = Dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly);
//Search all directories for plugins and return the paths
return GetPluginPaths(dirs).ToArray();
}
private static IEnumerable GetPluginPaths(IEnumerable dirs)
{
//Select only dirs with a dll that is named after the directory name
return dirs.Where(static pdir =>
{
string compined = Path.Combine(pdir.FullName, pdir.Name);
string FilePath = string.Concat(compined, PLUGIN_FILE_EXTENSION);
return FileOperations.FileExists(FilePath);
})
//Return the name of the dll file to import
.Select(static pdir =>
{
string compined = Path.Combine(pdir.FullName, pdir.Name);
return string.Concat(compined, PLUGIN_FILE_EXTENSION);
});
}
}
}
}