/* * Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime * File: PluginStackBuilder.cs * * PluginStackBuilder.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.Reflection; using System.Collections.Generic; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; namespace VNLib.Plugins.Runtime { /// /// A construction class used to build a single plugin stack. /// public sealed class PluginStackBuilder { private IPluginDiscoveryManager? DiscoveryManager; private bool HotReload; private TimeSpan ReloadDelay; private IPluginConfigReader? PluginConfig; private ILogProvider? DebugLog; private Func? Loader; /// /// Shortcut constructor for easy fluent chaining. /// /// A new public static PluginStackBuilder Create() => new(); /// /// Sets the plugin discovery manager used to find plugins /// /// The discovery manager instance /// The current builder instance for chaining public PluginStackBuilder WithDiscoveryManager(IPluginDiscoveryManager discoveryManager) { DiscoveryManager = discoveryManager; return this; } /// /// Enables hot reloading of the plugin assembly /// /// The delay time after a change is detected before the assembly is reloaded /// The current builder instance for chaining public PluginStackBuilder EnableHotReload(TimeSpan reloadDelay) { HotReload = true; ReloadDelay = reloadDelay; return this; } /// /// Specifies the JSON host configuration data to pass to the plugin /// /// The plugin configuration data /// The current builder instance for chaining public PluginStackBuilder WithConfigurationReader(IPluginConfigReader pluginConfig) { ArgumentNullException.ThrowIfNull(pluginConfig); //Store binary copy PluginConfig = pluginConfig; return this; } /// /// The factory callback function used to get assembly loaders for /// discovered plugins /// /// The factory callback funtion /// The current builder instance for chaining public PluginStackBuilder WithLoaderFactory(Func loaderFactory) { Loader = loaderFactory; return this; } /// /// Specifies the optional debug log provider to use for the plugin loader. /// /// The optional log provider instance ///The current builder instance for chaining public PluginStackBuilder WithDebugLog(ILogProvider logProvider) { DebugLog = logProvider; return this; } /// /// Creates a snapshot of the current builder state and builds a plugin stack /// /// The current builder instance for chaining /// public IPluginStack ConfigureStack() { _ = DiscoveryManager ?? throw new ArgumentException("You must specify a plugin discovery manager"); _ = PluginConfig ?? throw new ArgumentException("A plugin confuration reader must be specified"); //Clone the current builder state PluginStackBuilder clone = (PluginStackBuilder)MemberwiseClone(); return new PluginStack(clone); } /* * */ internal sealed record class PluginStack(PluginStackBuilder Builder) : IPluginStack { private readonly LinkedList _plugins = new(); /// public IReadOnlyCollection Plugins => _plugins; /// public void BuildStack() { //Discover all plugins IPluginAssemblyLoader[] loaders = DiscoverPlugins(Builder.DebugLog); //Create a loader for each plugin foreach (IPluginAssemblyLoader loader in loaders) { RuntimePluginLoader plugin = new(loader, Builder.DebugLog); _plugins.AddLast(plugin); } } private IPluginAssemblyLoader[] DiscoverPlugins(ILogProvider? debugLog) { //Select only dirs with a dll that is named after the directory name IEnumerable pluginPaths = Builder.DiscoveryManager!.DiscoverPluginFiles(); //Log the found plugin files IEnumerable pluginFileNames = pluginPaths.Select(static s => $"{Path.GetFileName(s)}\n"); debugLog?.Debug("Found plugin assemblies: \n{files}", string.Concat(pluginFileNames)); LinkedList loaders = new (); //Create a loader for each plugin foreach (string pluginPath in pluginPaths) { PlugingAssemblyConfig pConf = new(Builder.PluginConfig!) { AssemblyFile = pluginPath, WatchForReload = Builder.HotReload, ReloadDelay = Builder.ReloadDelay, Unloadable = Builder.HotReload }; //Get assembly loader from the configration IAssemblyLoader loader = Builder.Loader!.Invoke(pConf); //Add to list loaders.AddLast(new PluginAsmLoader(loader, pConf)); } return loaders.ToArray(); } /// public void Dispose() { //dispose all plugins _plugins.TryForeach(static p => p.Dispose()); _plugins.Clear(); } } internal sealed record class PluginAsmLoader(IAssemblyLoader Loader, IPluginAssemblyLoadConfig Config) : IPluginAssemblyLoader { /// public void Dispose() => Loader.Dispose(); /// public Assembly GetAssembly() => Loader.GetAssembly(); /// public void Load() => Loader.Load(); /// public void Unload() => Loader.Unload(); } internal sealed record class PlugingAssemblyConfig(IPluginConfigReader Config) : IPluginAssemblyLoadConfig { /// public bool Unloadable { get; init; } /// public string AssemblyFile { get; init; } = string.Empty; /// public bool WatchForReload { get; init; } /// public TimeSpan ReloadDelay { get; init; } /// public void ReadConfigurationData(Stream outputStream) => Config.ReadPluginConfigData(this, outputStream); } } }