/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime * File: RuntimePluginLoader.cs * * RuntimePluginLoader.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.Text.Json; using System.Reflection; using System.Threading.Tasks; using McMaster.NETCore.Plugins; using VNLib.Utils; using VNLib.Utils.IO; using VNLib.Utils.Logging; namespace VNLib.Plugins.Runtime { /// /// A runtime .NET assembly loader specialized to load /// assemblies that export types. /// public sealed class RuntimePluginLoader : VnDisposeable { private readonly PluginLoader Loader; private readonly string PluginPath; private readonly JsonDocument HostConfig; private readonly ILogProvider? Log; /// /// Gets the plugin lifetime manager. /// public PluginController Controller { get; } /// /// The path of the plugin's configuration file. (Default = pluginPath.json) /// public string PluginConfigPath { get; } /// /// Creates a new with the specified config and host config dom. /// /// The plugin's loader configuration /// The host/process configuration DOM /// A log provider to write plugin unload log events to /// public RuntimePluginLoader(PluginConfig config, JsonElement? hostConfig, ILogProvider? log) { //Default to empty config if null, otherwise clone a copy of the host config element HostConfig = hostConfig.HasValue ? Clone(hostConfig.Value) : JsonDocument.Parse("{}"); Loader = new(config); PluginPath = config.MainAssemblyPath; Log = log; //Only regiser reload handler if the load context is unloadable if (config.IsUnloadable) { //Init reloaded event handler Loader.Reloaded += Loader_Reloaded; } //Set the config path default PluginConfigPath = Path.ChangeExtension(PluginPath, ".json"); //Init container Controller = new(); } private async void Loader_Reloaded(object sender, PluginReloadedEventArgs eventArgs) { try { //All plugins must be unloaded forst UnloadAll(); //Reload the assembly and await InitializeController(); //Load plugins LoadPlugins(); } catch (Exception ex) { Log?.Error("Failed reload plugins for {loader}\n{ex}", PluginPath, ex); } } /// /// Initializes the plugin loader, and populates the /// with initialized plugins. /// /// A task that represents the initialization /// /// public async Task InitializeController() { JsonDocument? pluginConfig = null; try { //Get the plugin's configuration file if (FileOperations.FileExists(PluginConfigPath)) { pluginConfig = await this.GetPluginConfigAsync(); } else { //Set plugin config dom to an empty object if the file does not exist pluginConfig = JsonDocument.Parse("{}"); } //Load the main assembly Assembly PluginAsm = Loader.LoadDefaultAssembly(); //Init container from the assembly Controller.InitializePlugins(PluginAsm); string[] cliArgs = Environment.GetCommandLineArgs(); //Configure log/doms Controller.ConfigurePlugins(HostConfig, pluginConfig, cliArgs); } finally { pluginConfig?.Dispose(); } } /// /// Loads all configured plugins by calling /// event hook on the current thread. Loading exceptions are aggregated so not /// to block individual loading. /// /// public void LoadPlugins() => Controller.LoadPlugins(); /// /// Manually reload the internal /// which will reload the assembly and its plugins /// public void ReloadPlugins() => Loader.Reload(); /// /// Attempts to unload all plugins. /// /// public void UnloadAll() => Controller.UnloadPlugins(); /// protected override void Free() { Controller.Dispose(); Loader.Dispose(); HostConfig.Dispose(); } private static JsonDocument Clone(JsonElement hostConfig) { //Crate ms to write the current doc data to using VnMemoryStream ms = new(); using (Utf8JsonWriter writer = new(ms)) { hostConfig.WriteTo(writer); } //Reset ms ms.Seek(0, SeekOrigin.Begin); return JsonDocument.Parse(ms); } } }