/* * Copyright (c) 2023 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.IO; 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 { private bool _loaded; /// /// 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; } /// /// The assembly that this plugin was created from /// public Assembly OriginAsm { get; } /// /// The exposed runtime type of the plugin. Equivalent to /// calling Plugin.GetType() /// public Type PluginType { get; } private ConsoleEventHandlerSignature? PluginConsoleHandler; internal LivePlugin(IPlugin plugin, Assembly originAsm) { Plugin = plugin; OriginAsm = originAsm; 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 using JsonDocument merged = hostConfig.Merge(pluginConf, "host", PluginType.Name); //Write the config to binary to pass it to the plugin using VnMemoryStream vms = new(); using (Utf8JsonWriter writer = new(vms)) { merged.WriteTo(writer); } //Reset memstream vms.Seek(0, System.IO.SeekOrigin.Begin); //Invoke configInit.Invoke(vms.AsSpan()); } /// /// 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() { //Load and set loaded flag Plugin?.Load(); _loaded = true; } /// /// Unloads the plugin, only if the plugin was successfully loaded by /// calling the event hook. /// internal void UnloadPlugin() { //Remove delegate handler to the plugin to remove refs PluginConsoleHandler = null; //Only call unload if the plugin successfully loaded if (!_loaded) { return; } try { Plugin?.Unload(); } finally { Plugin = 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"); } } }