/* * Copyright (c) 2022 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime * File: LivePlugin.cs * * LivePlugin.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.Linq; using System.Reflection; using System.Text.Json; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Plugins.Attributes; namespace VNLib.Plugins.Runtime { /// /// /// Wrapper for a loaded instance, used internally /// for a single instance. /// /// /// Lifetime: for the existance of a single loaded /// plugin instance. Created once per loaded plugin instance. Once the plugin /// is unloaded, it is no longer useable. /// /// public class LivePlugin : IEquatable, IEquatable { /// /// The plugin's property during load time /// /// public string PluginName => Plugin?.PluginName ?? throw new InvalidOperationException("Plugin is not loaded"); /// /// The underlying that is warpped /// by he current instance /// public IPlugin? Plugin { get; private set; } private readonly Type PluginType; private ConsoleEventHandlerSignature? PluginConsoleHandler; internal LivePlugin(IPlugin plugin) { Plugin = plugin; PluginType = plugin.GetType(); GetConsoleHandler(); } private void GetConsoleHandler() { //Get the console handler method from the plugin instance MethodInfo? handler = (from m in PluginType.GetMethods() where m.GetCustomAttribute() != null select m) .FirstOrDefault(); //Get a delegate handler for the plugin PluginConsoleHandler = handler?.CreateDelegate(Plugin); } /// /// Sets the plugin's configuration if it defines a /// on an instance method /// /// The host configuration DOM /// The plugin local configuration DOM internal void InitConfig(JsonDocument hostConfig, JsonDocument pluginConf) { //Get the console handler method from the plugin instance MethodInfo? confHan = PluginType.GetMethods().Where(static m => m.GetCustomAttribute() != null) .FirstOrDefault(); //Get a delegate handler for the plugin ConfigInitializer? configInit = confHan?.CreateDelegate(Plugin); if (configInit == null) { return; } //Merge configurations before passing to plugin JsonDocument merged = hostConfig.Merge(pluginConf, "host", PluginType.Name); try { //Invoke configInit.Invoke(merged); } catch { merged.Dispose(); throw; } } /// /// Invokes the plugin's log initalizer method if it defines a /// on an instance method /// /// The current process's CLI args internal void InitLog(string[] cliArgs) { //Get the console handler method from the plugin instance MethodInfo? logInit = (from m in PluginType.GetMethods() where m.GetCustomAttribute() != null select m) .FirstOrDefault(); //Get a delegate handler for the plugin LogInitializer? logFunc = logInit?.CreateDelegate(Plugin); //Invoke logFunc?.Invoke(cliArgs); } /// /// Invokes the plugins console event handler if the type has one /// and the plugin is loaded. /// /// The message to pass to the plugin handler /// /// True if the command was sent to the plugin, false if the plugin is /// unloaded or did not export a console event handler /// public bool SendConsoleMessage(string message) { //Make sure plugin is loaded and has a console handler if (PluginConsoleHandler == null) { return false; } //Invoke plugin console handler PluginConsoleHandler(message); return true; } /// /// Calls the method on the plugin if its loaded /// internal void LoadPlugin() => Plugin?.Load(); /// /// Unloads all loaded endpoints from /// that they were loaded to, then unloads the plugin. /// /// An optional log provider to write unload exceptions to /// /// If is no null unload exceptions are swallowed and written to the log /// internal void UnloadPlugin(ILogProvider? logSink) { /* * We need to swallow plugin unload errors to avoid * unknown state, making sure endpoints are properly * unloaded! */ try { //Unload the plugin Plugin?.Unload(); } catch (Exception ex) { //Create an unload wrapper for the exception PluginUnloadException wrapper = new("Exception raised during plugin unload", ex); if (logSink == null) { throw wrapper; } //Write error to log sink logSink.Error(wrapper); } Plugin = null; PluginConsoleHandler = null; } /// public override bool Equals(object? obj) { Type? pluginType = Plugin?.GetType(); Type? otherType = obj?.GetType(); if(pluginType == null || otherType == null) { return false; } //If the other plugin is the same type as the current instance return true return pluginType.FullName == otherType.FullName; } /// public bool Equals(LivePlugin? other) { return Equals(other?.Plugin); } /// public bool Equals(IPlugin? other) { return Equals((object?)other); } /// public override int GetHashCode() { return Plugin?.GetHashCode() ?? throw new InvalidOperationException("Plugin is null"); } } }