From 0b4e18b9a7d8e0aea23aef7efd3707674f223b2b Mon Sep 17 00:00:00 2001 From: vnugent Date: Tue, 19 Sep 2023 22:11:00 -0400 Subject: Experimental plugin runtime updates --- lib/Hashing.Portable/src/Argon2/VnArgon2.cs | 2 +- lib/Net.Http/src/Core/HttpServerBase.cs | 10 +- .../src/HttpServiceStack.cs | 21 +- .../src/HttpServiceStackBuilder.cs | 75 ++++-- .../src/IManagedPlugin.cs | 55 ++++ .../src/IPluginAssemblyLoaderFactory.cs | 43 ---- .../src/IPluginController.cs | 19 +- .../src/IPluginLoadConfiguration.cs | 62 ----- .../src/IPluginWrapper.cs | 54 ---- .../src/IServiceHost.cs | 2 +- .../src/ManagedPlugin.cs | 89 ++----- .../src/PluginExtensions.cs | 60 +++++ .../src/PluginManager.cs | 193 ++++++-------- .../src/ServiceDomain.cs | 49 +--- .../src/ServiceGroup.cs | 2 +- .../src/Accounts/AccountUtils.cs | 2 +- .../src/Endpoints/IVirtualEndpoint.cs | 43 ++++ .../src/Endpoints/VfReturnType.cs | 61 +++++ lib/Plugins.Essentials/src/EventProcessor.cs | 1 + .../src/IVirtualEndpointTable.cs | 2 + .../src/SemiConsistentVeTable.cs | 1 + lib/Plugins.PluginBase/src/PluginBase.cs | 21 +- lib/Plugins.Runtime/src/IAssemblyLoader.cs | 51 ++++ lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs | 22 +- lib/Plugins.Runtime/src/IPluginConfig.cs | 7 + lib/Plugins.Runtime/src/IPluginDiscoveryManager.cs | 39 +++ lib/Plugins.Runtime/src/IPluginStack.cs | 47 ++++ lib/Plugins.Runtime/src/LivePlugin.cs | 25 +- lib/Plugins.Runtime/src/LoaderExtensions.cs | 222 ++++++++++++++-- lib/Plugins.Runtime/src/PluginController.cs | 6 +- lib/Plugins.Runtime/src/PluginStackBuilder.cs | 278 +++++++++++++++++++++ lib/Plugins.Runtime/src/RuntimePluginLoader.cs | 83 ++---- lib/Plugins/src/IEndpoint.cs | 37 --- lib/Plugins/src/IPlugin.cs | 15 +- lib/Plugins/src/IVirtualEndpoint.cs | 43 ---- lib/Plugins/src/VNLib.Plugins.csproj | 4 - lib/Plugins/src/VfReturnType.cs | 61 ----- lib/Plugins/src/Web/IEndpoint.cs | 37 +++ lib/Plugins/src/Web/IWebPlugin.cs | 45 ++++ lib/Plugins/src/Web/WebMessage.cs | 47 ++++ lib/Plugins/src/WebMessage.cs | 47 ---- 41 files changed, 1203 insertions(+), 780 deletions(-) create mode 100644 lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IPluginLoadConfiguration.cs delete mode 100644 lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs create mode 100644 lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs create mode 100644 lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs create mode 100644 lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs create mode 100644 lib/Plugins.Runtime/src/IAssemblyLoader.cs create mode 100644 lib/Plugins.Runtime/src/IPluginDiscoveryManager.cs create mode 100644 lib/Plugins.Runtime/src/IPluginStack.cs create mode 100644 lib/Plugins.Runtime/src/PluginStackBuilder.cs delete mode 100644 lib/Plugins/src/IEndpoint.cs delete mode 100644 lib/Plugins/src/IVirtualEndpoint.cs delete mode 100644 lib/Plugins/src/VfReturnType.cs create mode 100644 lib/Plugins/src/Web/IEndpoint.cs create mode 100644 lib/Plugins/src/Web/IWebPlugin.cs create mode 100644 lib/Plugins/src/Web/WebMessage.cs delete mode 100644 lib/Plugins/src/WebMessage.cs (limited to 'lib') diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index 9650128..eeeb70b 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -48,7 +48,7 @@ namespace VNLib.Hashing public const uint HASH_SIZE = 128; public const int MAX_SALT_SIZE = 100; public const string ID_MODE = "argon2id"; - public const string ARGON2_DEFUALT_LIB_NAME = "Argon2"; + public const string ARGON2_DEFUALT_LIB_NAME = "argon2"; public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "ARGON2_DLL_PATH"; private static readonly Encoding LocEncoding = Encoding.Unicode; diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs index 9fb1169..790d0ff 100644 --- a/lib/Net.Http/src/Core/HttpServerBase.cs +++ b/lib/Net.Http/src/Core/HttpServerBase.cs @@ -321,6 +321,10 @@ namespace VNLib.Net.Http //Closing, exit loop break; } + catch(AuthenticationException ae) when(ae.HResult == INVALID_FRAME_HRESULT) + { + Config.ServerLog.Debug("A TLS connection attempt was made but an invalid TLS frame was received"); + } catch (AuthenticationException ae) { Config.ServerLog.Error(ae); @@ -359,13 +363,13 @@ namespace VNLib.Net.Http { //Ignore aborted messages case SocketError.ConnectionAborted: - return; + break; case SocketError.ConnectionReset: Config.ServerLog.Debug("Connecion reset by client"); - return; + break; case SocketError.TimedOut: Config.ServerLog.Debug("Socket operation timed out"); - return; + break; default: Config.ServerLog.Information(se); break; diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs index ae0c522..51c3091 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs @@ -22,7 +22,6 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -30,6 +29,7 @@ using System.Collections.Generic; using VNLib.Utils; using VNLib.Net.Http; +using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -41,6 +41,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack { private readonly LinkedList _servers; private readonly ServiceDomain _serviceDomain; + private readonly PluginManager _plugins; private CancellationTokenSource? _cts; private Task WaitForAllTask; @@ -51,19 +52,21 @@ namespace VNLib.Plugins.Essentials.ServiceStack public IReadOnlyCollection Servers => _servers; /// - /// The service domain's plugin controller + /// Gets the internal that manages plugins for the entire + /// /// - public IPluginManager PluginManager => _serviceDomain.PluginManager; + public IHttpPluginManager PluginManager => _plugins; /// /// Initializes a new that will /// generate servers to listen for services exposed by the /// specified host context /// - internal HttpServiceStack(LinkedList servers, ServiceDomain serviceDomain) + internal HttpServiceStack(LinkedList servers, ServiceDomain serviceDomain, IPluginStack plugins) { _servers = servers; _serviceDomain = serviceDomain; + _plugins = new(serviceDomain, plugins); WaitForAllTask = Task.CompletedTask; } @@ -94,9 +97,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack } /// - /// Stops listening on all configured servers - /// and returns a task that completes when the service - /// host has stopped all servers and unloaded resources + /// Stops listening on all configured servers and returns a task that completes + /// when the service host has stopped all servers and unloaded resources /// /// The task that completes when public Task StopAndWaitAsync() @@ -109,6 +111,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack private void OnAllServerExit(Task allExit) { + //Unload plugins + _plugins.UnloadPlugins(); + //Unload the hosts _serviceDomain.TearDown(); } @@ -118,7 +123,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack { _cts?.Dispose(); - _serviceDomain.Dispose(); + _plugins.Dispose(); //remove all lists _servers.Clear(); diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs index 6784468..5997cf9 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using VNLib.Net.Http; +using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -41,17 +42,18 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// specified host context /// public HttpServiceStackBuilder() - {} + { } private Action>? _hostBuilder; private Func? _getServers; + private Func? _getPlugins; /// /// Uses the supplied callback to get a collection of virtual hosts /// to build the current domain with /// /// The callback method to build virtual hosts - /// A value that indicates if any virtual hosts were successfully loaded + /// The current instance for chaining public HttpServiceStackBuilder WithDomainBuilder(Action> hostBuilder) { _hostBuilder = hostBuilder; @@ -62,12 +64,24 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// Spcifies a callback function that builds instances from the hosts /// /// A callback method that gets the http server implementation for the service group + /// The current instance for chaining public HttpServiceStackBuilder WithHttp(Func getServers) { _getServers = getServers; return this; } + /// + /// Enables the stack to support plugins + /// + /// The callback function that returns the plugin stack when requested + /// The current instance for chaining + public HttpServiceStackBuilder WithPluginStack(Func getStack) + { + _getPlugins = getStack; + return this; + } + /// /// Builds the new from the configured callbacks, WITHOUT loading plugins /// @@ -80,33 +94,46 @@ namespace VNLib.Plugins.Essentials.ServiceStack //Inint the service domain ServiceDomain sd = new(); - try - { - if (!sd.BuildDomain(_hostBuilder)) - { - throw new ArgumentException("Failed to configure the service domain, you must expose at least one service host"); - } - - LinkedList servers = new(); - //enumerate hosts groups - foreach (ServiceGroup hosts in sd.ServiceGroups) - { - //Create new server - IHttpServer server = _getServers.Invoke(hosts); + if (!sd.BuildDomain(_hostBuilder)) + { + throw new ArgumentException("Failed to configure the service domain, you must expose at least one service host"); + } - //Add server to internal list - servers.AddLast(server); - } + LinkedList servers = new(); - //Return the service stack - return new HttpServiceStack(servers, sd); - } - catch + //enumerate hosts groups + foreach (ServiceGroup hosts in sd.ServiceGroups) { - sd.Dispose(); - throw; + //Create new server + IHttpServer server = _getServers.Invoke(hosts); + + //Add server to internal list + servers.AddLast(server); } + + //Only load plugins if the callback is configured + IPluginStack? plugins = _getPlugins?.Invoke(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + plugins ??= new EmptyPluginStack(); +#pragma warning restore CA2000 // Dispose objects before losing scope + + return new(servers, sd, plugins); + } + + /* + * An empty plugin stack that is used when the plugin callback is not configured + */ + private sealed class EmptyPluginStack : IPluginStack + { + public IReadOnlyCollection Plugins { get; } = Array.Empty(); + + public void BuildStack() + { } + + public void Dispose() + { } } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs new file mode 100644 index 0000000..c883e29 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IManagedPlugin.cs +* +* IManagedPlugin.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero 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.Essentials.ServiceStack 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using VNLib.Plugins.Runtime; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + + + /// + /// Represents a plugin managed by a that includes dynamically loaded plugins + /// + public interface IManagedPlugin + { + /// + /// Exposes the internal for the loaded plugin + /// + PluginController Controller { get; } + + /// + /// The file path to the loaded plugin + /// + string PluginPath { get; } + + /// + /// The exposed services the inernal plugin provides + /// + /// + /// WARNING: Services exposed by the plugin will abide by the plugin lifecycle, so consumers + /// must listen for plugin load/unload events to respect lifecycles properly. + /// + IUnloadableServiceProvider Services { get; } + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs deleted file mode 100644 index 4cb5741..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IPluginAssemblyLoaderFactory.cs -* -* IPluginAssemblyLoaderFactory.cs is part of VNLib.Plugins.Essentials.ServiceStack -* which is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero 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.Essentials.ServiceStack 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 Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using VNLib.Plugins.Runtime; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - /// - /// Represents a provider that creates a unique for the the - /// loaded assembly file. - /// - public interface IPluginAssemblyLoaderFactory - { - /// - /// Gets a new and unique instance for a given plugin assembly - /// file path - /// - /// The file path to the requested plugin assembly file - /// The new to recover the plugin assembly manifest - IPluginAssemblyLoader GetLoaderForPluginFile(string pluginFile); - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs index 3394208..6cece1f 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack -* File: IPluginManager.cs +* File: IHttpPluginManager.cs * -* IPluginManager.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* IHttpPluginManager.cs is part of VNLib.Plugins.Essentials.ServiceStack which * is part of the larger VNLib collection of libraries and utilities. * * VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify @@ -25,30 +25,19 @@ using System; using System.Collections.Generic; -using VNLib.Utils.Logging; - namespace VNLib.Plugins.Essentials.ServiceStack { /// /// Represents a live plugin controller that manages all /// plugins loaded in a /// - public interface IPluginManager + public interface IHttpPluginManager { /// - /// The the plugins managed by this + /// The the plugins managed by this /// public IEnumerable Plugins { get; } - /// - /// Loads all plugins specified by the host config to the service manager, - /// or attempts to load plugins by the default - /// - /// The plugin loading configuration - /// A log provider to write message and errors to - /// A task that resolves when all plugins are loaded - void LoadPlugins(IPluginLoadConfiguration config, ILogProvider appLog); - /// /// Sends a message to a plugin identified by it's name. /// diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginLoadConfiguration.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginLoadConfiguration.cs deleted file mode 100644 index 1ec528e..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginLoadConfiguration.cs +++ /dev/null @@ -1,62 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IPluginLoadConfiguration.cs -* -* IPluginLoadConfiguration.cs is part of VNLib.Plugins.Essentials.ServiceStack -* which is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero 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.Essentials.ServiceStack 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 Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System; -using System.Text.Json; - -using VNLib.Utils.Logging; -using VNLib.Plugins.Runtime; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - - /// - /// Plugin loading configuration variables - /// - public interface IPluginLoadConfiguration - { - /// - /// The directory containing the dynamic plugin assemblies to load - /// - string PluginDir { get; } - - /// - /// The optional host configuration file to merge with plugin config - /// to pass to the loading plugin. - /// - JsonElement? HostConfig { get; } - - /// - /// Passed to the underlying - /// holding plugins - /// - ILogProvider? PluginErrorLog { get; } - - /// - /// A factory instance the provides new instances - /// on demand from its plugin assembly path - /// - public IPluginAssemblyLoaderFactory AssemblyLoaderFactory { get; } - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs deleted file mode 100644 index 2e686ee..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs +++ /dev/null @@ -1,54 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.ServiceStack -* File: IPluginWrapper.cs -* -* IPluginWrapper.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero 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.Essentials.ServiceStack 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 Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using VNLib.Plugins.Runtime; - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - - /// - /// Represents a plugin managed by a that includes dynamically loaded plugins - /// - public interface IManagedPlugin - { - /// - /// Exposes the internal for the loaded plugin - /// - PluginController Controller { get; } - - /// - /// The file path to the loaded plugin - /// - string PluginPath { get; } - - /// - /// The exposed services the inernal plugin provides - /// - /// - /// WARNING: Services exposed by the plugin will abide by the plugin lifecycle, so consumers - /// must listen for plugin load/unload events to respect lifecycles properly. - /// - IUnloadableServiceProvider Services { get; } - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs b/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs index bb4f65f..5d8fbe7 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs @@ -53,7 +53,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack void OnRuntimeServiceAttach(IManagedPlugin plugin, IEndpoint[] endpoints); /// - /// Called when a 's + /// Called when a 's /// unloads a given plugin, and its originally discovered endpoints /// /// The unloading plugin to detach diff --git a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs index 54f0090..0fe4c93 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs @@ -22,8 +22,6 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -using System; -using System.IO; using System.Linq; using System.Threading; using System.Reflection; @@ -36,28 +34,16 @@ using VNLib.Plugins.Attributes; namespace VNLib.Plugins.Essentials.ServiceStack { - internal sealed class ManagedPlugin : VnDisposeable, IPluginEventListener, IManagedPlugin + internal sealed class ManagedPlugin : VnDisposeable, IManagedPlugin { - private readonly IPluginEventListener _serviceDomainListener; - private readonly RuntimePluginLoader _plugin; + internal RuntimePluginLoader Plugin { get; } - private UnloadableServiceContainer? _services; - - public ManagedPlugin(RuntimePluginLoader loader, IPluginEventListener listener) - { - //configure the loader - _plugin = loader; - - //Register loading event listener before loading occurs - _plugin.Controller.Register(this, this); - - //Store listener to raise events - _serviceDomainListener = listener; - } + /// + public string PluginPath => Plugin.Config.AssemblyFile; + private UnloadableServiceContainer? _services; - /// - public string PluginPath => _plugin.Config.AssemblyFile; + public ManagedPlugin(RuntimePluginLoader loader) => Plugin = loader; /// public IUnloadableServiceProvider Services @@ -75,29 +61,16 @@ namespace VNLib.Plugins.Essentials.ServiceStack get { Check(); - return _plugin.Controller; + return Plugin.Controller; } } - internal string PluginFileName => Path.GetFileName(PluginPath); - - internal void InitializePlugins() - { - Check(); - _plugin.InitializeController(); - } - - internal void LoadPlugins() - { - Check(); - _plugin.LoadPlugins(); - } - /* - * Automatically called after the plugin has successfully loaded - * by event handlers below - */ - private void ConfigureServices() + * Automatically called after the plugin has successfully loaded + * by event handlers below + */ + + internal void OnPluginLoaded() { //If the service container is defined, dispose _services?.Dispose(); @@ -106,7 +79,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack _services = new(); //Get types from plugin - foreach (LivePlugin plugin in _plugin.Controller.Plugins) + foreach (LivePlugin plugin in Plugin.Controller.Plugins) { /* * Get the exposed configurator method if declared, @@ -122,42 +95,15 @@ namespace VNLib.Plugins.Essentials.ServiceStack } } - internal void ReloadPlugins() - { - Check(); - _plugin.ReloadPlugins(); - } - - internal void UnloadPlugins() - { - Check(); - - //unload plugins - _plugin.UnloadAll(); - - //Services will be cleaned up by the unload event - } - - void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) - { - //Initialize services after load, before passing event - ConfigureServices(); - - //Propagate event - _serviceDomainListener.OnPluginLoaded(controller, state); - } - - void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) + internal void OnPluginUnloaded() { //Cleanup services no longer in use. Plugin is still valid until this method returns using (_services) { - //Propagate event - _serviceDomainListener.OnPluginUnloaded(controller, state); - //signal service cancel before disposing _services?.SignalUnload(); } + //Remove ref to services _services = null; } @@ -166,10 +112,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack { //Dispose services _services?.Dispose(); - //Unregister the listener to cleanup resources - _plugin.Controller.Unregister(this); + //Dispose loader - _plugin.Dispose(); + Plugin.Dispose(); } diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs new file mode 100644 index 0000000..010039d --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginExtensions.cs +* +* PluginExtensions.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero 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.Essentials.ServiceStack 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System.Linq; +using System.Collections.Generic; + +using VNLib.Plugins.Runtime; +using VNLib.Utils.Logging; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + /// + /// Internal and service stack specific extensions for plugins + /// + public static class PluginExtensions + { + /// + /// Gets the endpoints exposed by the plugin + /// + /// + /// The enumeration of web endpoints + internal static IEnumerable GetEndpoints(this IPlugin plugin) => ((IWebPlugin)plugin).GetEndpoints(); + + /// + /// Gets only plugins that implement interface + /// + /// + /// + internal static IEnumerable GetOnlyWebPlugins(this PluginController controller) => controller.Plugins.Where(p => p.Plugin is IWebPlugin); + + /// + /// Loads all plugins that implement interface into the + /// service stack + /// + /// + /// A log provider for writing loading logs to + public static void LoadPlugins(this HttpServiceStack stack, ILogProvider logProvider) => (stack.PluginManager as PluginManager)!.LoadPlugins(logProvider); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs index 2013a58..208001a 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs @@ -29,9 +29,9 @@ using System.Linq; using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; +using System.Runtime.CompilerServices; using VNLib.Utils; -using VNLib.Utils.IO; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Plugins.Runtime; @@ -43,128 +43,90 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// A sealed type that manages the plugin interaction layer. Manages the lifetime of plugin /// instances, exposes controls, and relays stateful plugin events. /// - internal sealed class PluginManager : VnDisposeable, IPluginManager, IPluginEventListener + internal sealed class PluginManager : VnDisposeable, IHttpPluginManager, IPluginEventListener { - private const string PLUGIN_FILE_EXTENSION = ".dll"; + private readonly ConditionalWeakTable _managedPlugins; + private readonly ServiceDomain _dependents; + private readonly IPluginStack _stack; - private readonly List _plugins; - private readonly IReadOnlyCollection _dependents; - - - private IEnumerable _livePlugins => _plugins.SelectMany(static p => p.Controller.Plugins); + private IEnumerable _livePlugins => _managedPlugins.SelectMany(static p => p.Key.Plugins); /// /// The collection of internal controllers /// - public IEnumerable Plugins => _plugins; + public IEnumerable Plugins => _managedPlugins.Select(static p => p.Value); - public PluginManager(IReadOnlyCollection dependents) + public PluginManager(ServiceDomain dependents, IPluginStack stack) { - _plugins = new(); _dependents = dependents; + _stack = stack; + _managedPlugins = new(); } - /// - /// - public void LoadPlugins(IPluginLoadConfiguration config, ILogProvider appLog) + /// + /// Configures the manager to capture and manage plugins within a plugin stack + /// + /// + public void LoadPlugins(ILogProvider debugLog) { - Check(); - - //Load all virtual file assemblies withing the plugin folder - DirectoryInfo dir = new(config.PluginDir); - - if (!dir.Exists) - { - appLog.Warn("Plugin directory {dir} does not exist. No plugins were loaded", config.PluginDir); - return; - } - - appLog.Information("Loading managed plugins"); - - //Enumerate all dll files within this dir - IEnumerable dirs = dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly); - - //Select only dirs with a dll that is named after the directory name - IEnumerable pluginPaths = GetPluginPaths(dirs); + _ = _stack ?? throw new InvalidOperationException("Plugin stack has not been set."); - IEnumerable pluginFileNames = pluginPaths.Select(static s => $"{Path.GetFileName(s)}\n"); + //Build the plugin stack + _stack.BuildStack(); - appLog.Debug("Found plugin files: \n{files}", string.Concat(pluginFileNames)); + //Register for plugin events + _stack.RegsiterListener(this, this); - /* - * We need to get the assembly loader for the plugin file, then create its - * RuntimePluginLoader which will be passed to the Managed plugin instance - */ + //Create plugin wrappers from loaded plugins + ManagedPlugin[] wrapper = _stack.Plugins.Select(p => new ManagedPlugin(p)).ToArray(); - ManagedPlugin[] wrappers = pluginPaths.Select(pw => config.AssemblyLoaderFactory.GetLoaderForPluginFile(pw)) - .Select(l => new RuntimePluginLoader(l, config.HostConfig, config.PluginErrorLog)) - .Select(loader => new ManagedPlugin(loader, this)) - .ToArray(); + //Add all wrappers to the managed plugins table + Array.ForEach(wrapper, w => _managedPlugins.Add(w.Plugin.Controller, w)); - //Add to loaded plugins - _plugins.AddRange(wrappers); - - //Load plugins - InitiailzeAndLoad(appLog); - } - - 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); - }); - } - - private void InitiailzeAndLoad(ILogProvider debugLog) - { - //Load all async - _plugins.ToArray().TryForeach(p => InitializePlugin(p, debugLog)); + //Init remaining controllers single-threaded + _managedPlugins.Select(p => p.Value).TryForeach(w => InitializePlugin(w.Plugin, debugLog)); //Load stage, load all multithreaded - Parallel.ForEach(_plugins, p => LoadPlugin(p, debugLog)); + Parallel.ForEach(wrapper, wp => LoadPlugin(wp.Plugin, debugLog)); debugLog.Information("Plugin loading completed"); } - private void InitializePlugin(ManagedPlugin plugin, ILogProvider debugLog) + + private void InitializePlugin(RuntimePluginLoader plugin, ILogProvider debugLog) { - void LogAndRemovePlugin(Exception ex) + string fileName = Path.GetFileName(plugin.Config.AssemblyFile); + + try { - debugLog.Error(ex, $"Exception raised during initialzation of {plugin.PluginFileName}. It has been removed from the collection\n{ex}"); + //Initialzie plugin wrapper + plugin.InitializeController(); - //Remove the plugin from the list while locking it - lock (_plugins) + /* + * If the plugin assembly does not expose any plugin types or there is an issue loading the assembly, + * its types my not unify, then we should give the user feedback insead of a silent fail. + */ + if (!plugin.Controller.Plugins.Any()) { - _plugins.Remove(plugin); + debugLog.Warn("No plugin instances were exposed via {ams} assembly. This may be due to an assebmly mismatch", fileName); } - - //Dispose the plugin - plugin.Dispose(); - } - - try - { - //Load wrapper - plugin.InitializePlugins(); } catch (Exception ex) { - LogAndRemovePlugin(ex); + debugLog.Error("Exception raised during initialzation of {asm}. It has been removed from the collection\n{ex}", fileName, ex); + + //Remove the plugin from the table + _managedPlugins.Remove(plugin.Controller); + + //Dispose the plugin + plugin.Dispose(); } } - private static void LoadPlugin(ManagedPlugin plugin, ILogProvider debugLog) + private static void LoadPlugin(RuntimePluginLoader plugin, ILogProvider debugLog) { + string fileName = Path.GetFileName(plugin.Config.AssemblyFile); + Stopwatch sw = new(); try { @@ -175,23 +137,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack sw.Stop(); - /* - * If the plugin assembly does not expose any plugin types or there is an issue loading the assembly, - * its types my not unify, then we should give the user feedback insead of a silent fail. - */ - if (!plugin.Controller.Plugins.Any()) - { - debugLog.Warn("No plugin instances were exposed via {ams} assembly. This may be due to an assebmly mismatch", plugin.PluginFileName); - } - else - { - debugLog.Verbose("Loaded {pl} in {tm} ms", plugin.PluginFileName, sw.ElapsedMilliseconds); - } - + debugLog.Verbose("Loaded {pl} in {tm} ms", fileName, sw.ElapsedMilliseconds); } catch (Exception ex) { - debugLog.Error(ex, $"Exception raised during loading {plugin.PluginFileName}. Failed to load plugin \n{ex}"); + debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", fileName, ex); } finally { @@ -214,48 +164,63 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// public void ForceReloadAllPlugins() { - //Reload all plugin managers - _plugins.TryForeach(static p => p.ReloadPlugins()); + Check(); + + //Reload all plugins, causing an event cascade + _stack.ReloadAll(); } /// public void UnloadPlugins() { + Check(); + //Unload all plugin controllers - _plugins.TryForeach(static p => p.UnloadPlugins()); + _stack.UnloadAll(); /* * All plugin instances must be destroyed because the * only way they will be loaded is from their files * again, so they must be released */ - _plugins.TryForeach(static p => p.Dispose()); - _plugins.Clear(); + Free(); } protected override void Free() { - //Cleanup on dispose if unload failed - _plugins.TryForeach(static p => p.Dispose()); - _plugins.Clear(); + //Dispose all managed plugins and clear the table + _managedPlugins.TryForeach(p => p.Value.Dispose()); + _managedPlugins.Clear(); } void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) { + //Handle service events + ManagedPlugin mp = _managedPlugins.GetValue(controller, (pc) => null!); + + //Run onload method before invoking other handlers + mp.OnPluginLoaded(); + //Get event listeners at event time because deps may be modified by the domain - ServiceGroup[] deps = _dependents.Select(static d => d).ToArray(); + ServiceGroup[] deps = _dependents.ServiceGroups.Select(static d => d).ToArray(); //run onload method - deps.TryForeach(d => d.OnPluginLoaded((IManagedPlugin)state!)); + deps.TryForeach(d => d.OnPluginLoaded(mp)); } void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) { + //Handle service events + ManagedPlugin mp = _managedPlugins.GetValue(controller, (pc) => null!); + + //Run onload method before invoking other handlers + mp.OnPluginUnloaded(); + //Get event listeners at event time because deps may be modified by the domain - ServiceGroup[] deps = _dependents.Select(static d => d).ToArray(); + ServiceGroup[] deps = _dependents.ServiceGroups.Select(static d => d).ToArray(); //Run unloaded method - deps.TryForeach(d => d.OnPluginUnloaded((IManagedPlugin)state!)); + deps.TryForeach(d => d.OnPluginUnloaded(mp)); } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs index f0f9559..4cff671 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs @@ -27,7 +27,6 @@ using System.Net; using System.Linq; using System.Collections.Generic; -using VNLib.Utils; using VNLib.Utils.Extensions; namespace VNLib.Plugins.Essentials.ServiceStack @@ -37,31 +36,19 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// Represents a domain of services and thier dynamically loaded plugins /// that will be hosted by an application service stack /// - public sealed class ServiceDomain : VnDisposeable + public sealed class ServiceDomain { private readonly LinkedList _serviceGroups; - private readonly PluginManager _plugins; - + /// /// Gets all service groups loaded in the service manager /// public IReadOnlyCollection ServiceGroups => _serviceGroups; - /// - /// Gets the internal that manages plugins for the entire - /// - /// - public IPluginManager PluginManager => _plugins; - /// /// Initializes a new empty /// - public ServiceDomain() - { - _serviceGroups = new(); - //Init plugin manager and pass ref to service group collection - _plugins = new PluginManager(_serviceGroups); - } + public ServiceDomain() => _serviceGroups = new(); /// /// Uses the supplied callback to get a collection of virtual hosts @@ -69,11 +56,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// The callback method to build virtual hosts /// A value that indicates if any virtual hosts were successfully loaded - /// public bool BuildDomain(Action> hostBuilder) { - Check(); - //LL to store created hosts LinkedList hosts = new(); @@ -88,11 +72,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// The enumeration of virtual hosts /// A value that indicates if any virtual hosts were successfully loaded - /// public bool FromExisting(IEnumerable hosts) { - Check(); - //Get service groups and pass service group list CreateServiceGroups(_serviceGroups, hosts); return _serviceGroups.Any(); @@ -124,33 +105,15 @@ namespace VNLib.Plugins.Essentials.ServiceStack } /// - /// Tears down the service domain by unloading all plugins (calling their event handlers) - /// and destroying all s. This instance may be rebuilt if this - /// method returns successfully. + /// Tears down the service domain by destroying all s. This instance may be rebuilt + /// if this method returns successfully. /// - internal void TearDown() + public void TearDown() { - Check(); - - /* - * Unloading plugins should trigger the OnPluginUnloading - * hook which should cause all dependencies to unload linked - * types. - */ - _plugins.UnloadPlugins(); - //Manually cleanup if unload missed data _serviceGroups.TryForeach(static sg => sg.UnloadAll()); //empty service groups _serviceGroups.Clear(); } - - - /// - protected override void Free() - { - _plugins.Dispose(); - _serviceGroups.Clear(); - } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs index 2801776..c8be44c 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs @@ -83,7 +83,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack internal void OnPluginLoaded(IManagedPlugin controller) { //Get all new endpoints for plugin - IEndpoint[] newEndpoints = controller.Controller.Plugins.SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray(); + IEndpoint[] newEndpoints = controller.Controller.GetOnlyWebPlugins().SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray(); //Add endpoints to dict _endpointsForPlugins.AddOrUpdate(controller, newEndpoints); diff --git a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs index e819e6c..7f3ae40 100644 --- a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs +++ b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs @@ -478,7 +478,7 @@ namespace VNLib.Plugins.Essentials.Accounts /// /// True for a local account, false otherwise [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasLocalAccount(this in SessionInfo session, bool value) => session[LOCAL_ACCOUNT_ENTRY] = value ? "1" : null; + public static void HasLocalAccount(this in SessionInfo session, bool value) => session[LOCAL_ACCOUNT_ENTRY] = value ? "1" : null!; /// /// Gets a value indicating if the session belongs to a local user account diff --git a/lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs b/lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs new file mode 100644 index 0000000..10545cf --- /dev/null +++ b/lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: IVirtualEndpoint.cs +* +* IVirtualEndpoint.cs is part of VNLib.Plugins.Essentials which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials 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.Essentials 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. If not, see http://www.gnu.org/licenses/. +*/ + +using System.Threading.Tasks; + +namespace VNLib.Plugins.Essentials.Endpoints +{ + + /// + /// Represents a virtual page which provides processing on an entity + /// + /// The entity type to process + public interface IVirtualEndpoint : IEndpoint + { + /// + /// The handler method for processing the specified location. + /// + /// The current connection/session + /// A specifying how the caller should continue processing the request + public ValueTask Process(TEntity entity); + } +} \ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs b/lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs new file mode 100644 index 0000000..2fe29c7 --- /dev/null +++ b/lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: VfReturnType.cs +* +* VfReturnType.cs is part of VNLib.Plugins.Essentials which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials 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.Essentials 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.Essentials. If not, see http://www.gnu.org/licenses/. +*/ + +namespace VNLib.Plugins.Essentials +{ + + /// + /// Represents the result of a virutal endpoint processing operation + /// + public enum VfReturnType + { + /// + /// Signals that the virtual endpoint + /// + ProcessAsFile, + /// + /// Signals that the virtual endpoint generated a response, and + /// the connection should be completed + /// + VirtualSkip, + /// + /// Signals that the virtual endpoint determined that the connection + /// should be denied. + /// + Forbidden, + /// + /// Signals that the resource the virtual endpoint was processing + /// does not exist. + /// + NotFound, + /// + /// Signals that the virutal endpoint determined the request was invalid + /// + BadRequest, + /// + /// Signals that the virtual endpoint had an error + /// + Error + } +} diff --git a/lib/Plugins.Essentials/src/EventProcessor.cs b/lib/Plugins.Essentials/src/EventProcessor.cs index 90906eb..c4659b9 100644 --- a/lib/Plugins.Essentials/src/EventProcessor.cs +++ b/lib/Plugins.Essentials/src/EventProcessor.cs @@ -39,6 +39,7 @@ using VNLib.Plugins.Essentials.Content; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Essentials.Extensions; using VNLib.Plugins.Essentials.Middleware; +using VNLib.Plugins.Essentials.Endpoints; #nullable enable diff --git a/lib/Plugins.Essentials/src/IVirtualEndpointTable.cs b/lib/Plugins.Essentials/src/IVirtualEndpointTable.cs index cfe9661..c260b45 100644 --- a/lib/Plugins.Essentials/src/IVirtualEndpointTable.cs +++ b/lib/Plugins.Essentials/src/IVirtualEndpointTable.cs @@ -24,6 +24,8 @@ using System; +using VNLib.Plugins.Essentials.Endpoints; + #nullable enable namespace VNLib.Plugins.Essentials diff --git a/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs b/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs index d43432a..483dc35 100644 --- a/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs +++ b/lib/Plugins.Essentials/src/SemiConsistentVeTable.cs @@ -29,6 +29,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Net.Http; +using VNLib.Plugins.Essentials.Endpoints; #nullable enable diff --git a/lib/Plugins.PluginBase/src/PluginBase.cs b/lib/Plugins.PluginBase/src/PluginBase.cs index 258a923..bb42c97 100644 --- a/lib/Plugins.PluginBase/src/PluginBase.cs +++ b/lib/Plugins.PluginBase/src/PluginBase.cs @@ -44,7 +44,7 @@ namespace VNLib.Plugins /// Provides a concrete base class for instances using the Serilog logging provider. /// Accepts the standard plugin configuration constructors /// - public abstract class PluginBase : MarshalByRefObject, IPlugin, IPluginTaskObserver + public abstract class PluginBase : MarshalByRefObject, IWebPlugin, IPluginTaskObserver { /* * CTS exists for the life of the plugin, its resources are never disposed @@ -65,9 +65,15 @@ namespace VNLib.Plugins /// protected virtual string GlobalConfigDomPropertyName => "host"; + /// + /// The property name of the plugin configuration element in the plugin + /// runtime supplied configuration object. + /// + protected virtual string PluginConfigDomPropertyName => "plugin"; + /// /// A list of all currently prepared endpoints. - /// Endpoints must be added to this list before is called + /// Endpoints must be added to this list before is called /// by the host app /// public ICollection Endpoints { get; } = new List(); @@ -90,7 +96,7 @@ namespace VNLib.Plugins /// /// The configuration data from the plugin's config file passed by the host application /// - public JsonElement PluginConfig => Configuration.RootElement.GetProperty(GetType().Name); + public JsonElement PluginConfig => Configuration.RootElement.GetProperty(PluginConfigDomPropertyName); /// public abstract string PluginName { get; } @@ -252,7 +258,7 @@ namespace VNLib.Plugins /// The command message protected abstract void ProcessHostCommand(string cmd); - IEnumerable IPlugin.GetEndpoints() + IEnumerable IWebPlugin.GetEndpoints() { OnGetEndpoints(); return Endpoints; @@ -335,7 +341,10 @@ namespace VNLib.Plugins } //Wait for all tasks to complete for a maxium of 10 seconds - Task.WaitAll(tasks, TimeSpan.FromSeconds(10)); + if(!Task.WaitAll(tasks, TimeSpan.FromSeconds(10))) + { + Log.Error("Tasks failed to complete in the allotted timeout period"); + } } } @@ -379,7 +388,7 @@ namespace VNLib.Plugins protected abstract void OnUnLoad(); /// - /// Invoked before called by the host app to get all endpoints + /// Invoked before called by the host app to get all endpoints /// for the current plugin /// protected virtual void OnGetEndpoints() { } diff --git a/lib/Plugins.Runtime/src/IAssemblyLoader.cs b/lib/Plugins.Runtime/src/IAssemblyLoader.cs new file mode 100644 index 0000000..816b622 --- /dev/null +++ b/lib/Plugins.Runtime/src/IAssemblyLoader.cs @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: IAssemblyLoader.cs +* +* IAssemblyLoader.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.Reflection; + +namespace VNLib.Plugins.Runtime +{ + /// + /// Represents the core assembly loading functionality + /// + public interface IAssemblyLoader : IDisposable + { + /// + /// Unloads the assembly loader if Config.Unloadable is true, otherwise does nothing + /// + void Unload(); + + /// + /// Prepares the loader for use + /// + void Load(); + + /// + /// Begins the loading process and recovers the default assembly + /// + /// The main assembly from the assembly file + Assembly GetAssembly(); + } +} diff --git a/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs b/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs index 2d04703..d75ac47 100644 --- a/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs +++ b/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs @@ -22,36 +22,18 @@ * along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/. */ -using System; -using System.Reflection; - namespace VNLib.Plugins.Runtime { + /// /// Represents the bare assembly loader that gets a main assembly for a plugin and handles /// type resolution, while providing loading/unloading /// - public interface IPluginAssemblyLoader : IDisposable + public interface IPluginAssemblyLoader : IAssemblyLoader { /// /// Gets the plugin's configuration information /// IPluginConfig Config { get; } - - /// - /// Unloads the assembly loader if Config.Unloadable is true, otherwise does nothing - /// - void Unload(); - - /// - /// Prepares the loader for use - /// - void Load(); - - /// - /// Begins the loading process and recovers the default assembly - /// - /// The main assembly from the assembly file - Assembly GetAssembly(); } } diff --git a/lib/Plugins.Runtime/src/IPluginConfig.cs b/lib/Plugins.Runtime/src/IPluginConfig.cs index c3130f3..a460fc0 100644 --- a/lib/Plugins.Runtime/src/IPluginConfig.cs +++ b/lib/Plugins.Runtime/src/IPluginConfig.cs @@ -23,6 +23,7 @@ */ using System; +using System.IO; namespace VNLib.Plugins.Runtime { @@ -51,5 +52,11 @@ namespace VNLib.Plugins.Runtime /// The delay which a watcher should wait to trigger a plugin reload after an assembly file changes /// TimeSpan ReloadDelay { get; } + + /// + /// Reads the host configuration into the given stream + /// + /// The stream to write configurationd data to + void ReadConfigurationData(Stream outputStream); } } diff --git a/lib/Plugins.Runtime/src/IPluginDiscoveryManager.cs b/lib/Plugins.Runtime/src/IPluginDiscoveryManager.cs new file mode 100644 index 0000000..234a935 --- /dev/null +++ b/lib/Plugins.Runtime/src/IPluginDiscoveryManager.cs @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: IPluginDiscoveryManager.cs +* +* IPluginDiscoveryManager.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/. +*/ + +namespace VNLib.Plugins.Runtime +{ + /// + /// Represents a plugin discovery manager that, when requestesd, discovers + /// all plugin assembly files. + /// + public interface IPluginDiscoveryManager + { + /// + /// Gets all plugin assembly files that should be loaded + /// + /// + string[] DiscoverPluginFiles(); + } +} diff --git a/lib/Plugins.Runtime/src/IPluginStack.cs b/lib/Plugins.Runtime/src/IPluginStack.cs new file mode 100644 index 0000000..4bbd871 --- /dev/null +++ b/lib/Plugins.Runtime/src/IPluginStack.cs @@ -0,0 +1,47 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: IPluginStack.cs +* +* IPluginStack.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.Collections.Generic; + +namespace VNLib.Plugins.Runtime +{ + /// + /// Provides a container and functionality to manage an entire collection + /// of plugins. + /// + public interface IPluginStack : IDisposable + { + /// + /// The collection of all plugin loaders + /// + IReadOnlyCollection Plugins { get; } + + /// + /// Discovers all plugins for the runtime and populates + /// the collection. + /// + void BuildStack(); + } +} \ No newline at end of file diff --git a/lib/Plugins.Runtime/src/LivePlugin.cs b/lib/Plugins.Runtime/src/LivePlugin.cs index 67cacb4..573b520 100644 --- a/lib/Plugins.Runtime/src/LivePlugin.cs +++ b/lib/Plugins.Runtime/src/LivePlugin.cs @@ -25,10 +25,7 @@ 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 @@ -97,9 +94,8 @@ namespace VNLib.Plugins.Runtime /// 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) + /// The host configuration DOM + internal void InitConfig(ReadOnlySpan configData) { //Get the console handler method from the plugin instance MethodInfo? confHan = PluginType.GetMethods().Where(static m => m.GetCustomAttribute() != null) @@ -109,23 +105,10 @@ namespace VNLib.Plugins.Runtime 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()); + configInit.Invoke(configData); } /// diff --git a/lib/Plugins.Runtime/src/LoaderExtensions.cs b/lib/Plugins.Runtime/src/LoaderExtensions.cs index e244aa6..4dc1253 100644 --- a/lib/Plugins.Runtime/src/LoaderExtensions.cs +++ b/lib/Plugins.Runtime/src/LoaderExtensions.cs @@ -26,7 +26,10 @@ 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 { @@ -113,28 +116,7 @@ namespace VNLib.Plugins.Runtime reg.Register(listener, state); return new(reg, listener); } - - /// - /// Loads the configuration file into its format - /// for reading. - /// - /// - /// A new of the loaded configuration file - public static JsonDocument GetPluginConfig(this RuntimePluginLoader loader) - { - //Open and read the config file - using FileStream confStream = File.OpenRead(loader.PluginConfigPath); - - JsonDocumentOptions jdo = new() - { - AllowTrailingCommas = true, - CommentHandling = JsonCommentHandling.Skip, - }; - - //parse the plugin config file - return JsonDocument.Parse(confStream, jdo); - } - + /// /// Determines if the current /// exposes the desired type on is @@ -165,5 +147,201 @@ namespace VNLib.Plugins.Runtime 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); + }); + } + } } } diff --git a/lib/Plugins.Runtime/src/PluginController.cs b/lib/Plugins.Runtime/src/PluginController.cs index 53e0ae3..adb8ff9 100644 --- a/lib/Plugins.Runtime/src/PluginController.cs +++ b/lib/Plugins.Runtime/src/PluginController.cs @@ -24,10 +24,10 @@ using System; using System.Linq; -using System.Text.Json; using System.Reflection; using System.Collections.Generic; +using VNLib.Utils.IO; using VNLib.Utils.Extensions; namespace VNLib.Plugins.Runtime @@ -105,11 +105,11 @@ namespace VNLib.Plugins.Runtime } } - internal void ConfigurePlugins(JsonDocument hostDom, JsonDocument pluginDom, string[] cliArgs) + internal void ConfigurePlugins(VnMemoryStream configData, string[] cliArgs) { lock (_stateLock) { - _plugins.TryForeach(lp => lp.InitConfig(hostDom, pluginDom)); + _plugins.TryForeach(lp => lp.InitConfig(configData.AsSpan())); _plugins.TryForeach(lp => lp.InitLog(cliArgs)); } } diff --git a/lib/Plugins.Runtime/src/PluginStackBuilder.cs b/lib/Plugins.Runtime/src/PluginStackBuilder.cs new file mode 100644 index 0000000..5769f3e --- /dev/null +++ b/lib/Plugins.Runtime/src/PluginStackBuilder.cs @@ -0,0 +1,278 @@ +/* +* Copyright (c) 2023 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.Text; +using System.Text.Json; +using System.Reflection; +using System.Collections.Generic; + +using VNLib.Utils.IO; +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 byte[]? HostConfigData; + private ILogProvider? DebugLog; + + private Func? Loader; + + 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 current builder instance for chaining + public PluginStackBuilder WithConfigurationData(ReadOnlySpan hostConfig) + { + //Store binary copy + HostConfigData = hostConfig.ToArray(); + 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 BuildStack() + { + _ = DiscoveryManager ?? throw new ArgumentException("You must specify a plugin discovery manager"); + + //Create a default config if none was specified + HostConfigData ??= GetEmptyConfig(); + + //Clone the current builder state + PluginStackBuilder clone = (PluginStackBuilder)MemberwiseClone(); + + return new PluginStack(clone); + } + + private static byte[] GetEmptyConfig() => Encoding.UTF8.GetBytes("{}"); + + + /* + * + */ + 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.HostConfigData) + { + 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, IPluginConfig 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(ReadOnlyMemory HostConfig) : IPluginConfig + { + /// + public bool Unloadable { get; init; } + + /// + public string AssemblyFile { get; init; } = string.Empty; + + /// + public bool WatchForReload { get; init; } + + /// + public TimeSpan ReloadDelay { get; init; } + + /* + * The plugin config file is the same as the plugin assembly file, + * but with the .json extension + */ + private string PluginConfigFile => Path.ChangeExtension(AssemblyFile, ".json"); + + /// + public void ReadConfigurationData(Stream outputStream) + { + //Allow comments and trailing commas + JsonDocumentOptions jdo = new() + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip, + }; + + using JsonDocument hConfig = JsonDocument.Parse(HostConfig, jdo); + + //Read the plugin config file + if (FileOperations.FileExists(PluginConfigFile)) + { + //Open file stream to read data + using FileStream confStream = File.OpenRead(PluginConfigFile); + + //Parse the config file + using JsonDocument pConfig = JsonDocument.Parse(confStream, jdo); + + //Merge the configs + using JsonDocument merged = hConfig.Merge(pConfig,"host", "plugin"); + + //Write the merged config to the output stream + using Utf8JsonWriter writer = new(outputStream); + merged.WriteTo(writer); + } + else + { + byte[] pluginConfig = Encoding.UTF8.GetBytes("{}"); + + using JsonDocument pConfig = JsonDocument.Parse(pluginConfig, jdo); + + //Merge the configs + using JsonDocument merged = hConfig.Merge(pConfig,"host", "plugin"); + + //Write the merged config to the output stream + using Utf8JsonWriter writer = new(outputStream); + merged.WriteTo(writer); + } + } + } + } +} diff --git a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs index 03a15a1..21d4691 100644 --- a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs +++ b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs @@ -24,7 +24,6 @@ using System; using System.IO; -using System.Text.Json; using System.Reflection; using VNLib.Utils; @@ -42,7 +41,6 @@ namespace VNLib.Plugins.Runtime private static readonly IPluginAssemblyWatcher Watcher = new AssemblyWatcher(); private readonly IPluginAssemblyLoader Loader; - private readonly JsonDocument HostConfig; private readonly ILogProvider? Log; /// @@ -54,11 +52,6 @@ namespace VNLib.Plugins.Runtime /// Gets the plugin lifecycle controller /// public PluginController Controller { get; } - - /// - /// The path of the plugin's configuration file. (Default = pluginPath.json) - /// - public string PluginConfigPath => Path.ChangeExtension(Config.AssemblyFile, ".json"); /// /// Creates a new with the specified config and host config dom. @@ -67,14 +60,11 @@ namespace VNLib.Plugins.Runtime /// The host/process configuration DOM /// A log provider to write plugin unload log events to /// - public RuntimePluginLoader(IPluginAssemblyLoader loader, JsonElement? hostConfig, ILogProvider? log) + public RuntimePluginLoader(IPluginAssemblyLoader loader, ILogProvider? log) { Log = log; Loader = loader ?? throw new ArgumentNullException(nameof(loader)); - //Default to empty config if null, otherwise clone a copy of the host config element - HostConfig = hostConfig.HasValue ? Clone(hostConfig.Value) : JsonDocument.Parse("{}"); - //Configure watcher if requested if (loader.Config.WatchForReload) { @@ -94,39 +84,28 @@ namespace VNLib.Plugins.Runtime /// public void InitializeController() { - JsonDocument? pluginConfig = null; + //Prep the assembly loader + Loader.Load(); - try - { - //Prep the assembly loader - Loader.Load(); - - //Get the plugin's configuration file - if (FileOperations.FileExists(PluginConfigPath)) - { - pluginConfig = this.GetPluginConfig(); - } - 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.GetAssembly(); - - //Init container from the assembly - Controller.InitializePlugins(PluginAsm); - - string[] cliArgs = Environment.GetCommandLineArgs(); - - //Configure log/doms - Controller.ConfigurePlugins(HostConfig, pluginConfig, cliArgs); - } - finally - { - pluginConfig?.Dispose(); - } + //Load the main assembly + Assembly PluginAsm = Loader.GetAssembly(); + + //Init container from the assembly + Controller.InitializePlugins(PluginAsm); + + string[] cliArgs = Environment.GetCommandLineArgs(); + + //Write the config to binary to pass it to the plugin + using VnMemoryStream vms = new(); + + //Read config data + Loader.Config.ReadConfigurationData(vms); + + //Reset memstream + vms.Seek(0, SeekOrigin.Begin); + + //Configure log/doms + Controller.ConfigurePlugins(vms, cliArgs); } /// @@ -218,25 +197,7 @@ namespace VNLib.Plugins.Runtime //Cleanup Controller.Dispose(); - HostConfig.Dispose(); Loader.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); - } } } \ No newline at end of file diff --git a/lib/Plugins/src/IEndpoint.cs b/lib/Plugins/src/IEndpoint.cs deleted file mode 100644 index 33d49df..0000000 --- a/lib/Plugins/src/IEndpoint.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: IEndpoint.cs -* -* IEndpoint.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -namespace VNLib.Plugins -{ - /// - /// A base class for all entity processing endpoints to listen for requests - /// - public interface IEndpoint - { - /// - /// The location path for which to match this handler - /// - public string Path { get; } - } -} \ No newline at end of file diff --git a/lib/Plugins/src/IPlugin.cs b/lib/Plugins/src/IPlugin.cs index d872232..c1fa6a6 100644 --- a/lib/Plugins/src/IPlugin.cs +++ b/lib/Plugins/src/IPlugin.cs @@ -22,9 +22,6 @@ * along with VNLib.Plugins. If not, see http://www.gnu.org/licenses/. */ -using System; -using System.Collections.Generic; - namespace VNLib.Plugins { /// @@ -36,23 +33,15 @@ namespace VNLib.Plugins /// The name of the plugin to referrence (may be used by the host to interact) /// string PluginName { get; } + /// /// Performs operations to prepare the plugin for use /// void Load(); + /// /// Invoked when the plugin is unloaded from the runtime /// void Unload(); - /// - /// Returns all endpoints within the plugin to load into the current root - /// - /// An enumeration of endpoints to load - /// - /// Lifecycle: Results returned from this method should be consistant (although its only - /// likely to be called once) anytime after the method, and undefined - /// after the method is called. - /// - IEnumerable GetEndpoints(); } } \ No newline at end of file diff --git a/lib/Plugins/src/IVirtualEndpoint.cs b/lib/Plugins/src/IVirtualEndpoint.cs deleted file mode 100644 index 5f33c0f..0000000 --- a/lib/Plugins/src/IVirtualEndpoint.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: IVirtualEndpoint.cs -* -* IVirtualEndpoint.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -using System.Threading.Tasks; - -namespace VNLib.Plugins -{ - - /// - /// Represents a virtual page which provides processing on an entity - /// - /// The entity type to process - public interface IVirtualEndpoint : IEndpoint - { - /// - /// The handler method for processing the specified location. - /// - /// The current connection/session - /// A specifying how the caller should continue processing the request - public ValueTask Process(TEntity entity); - } -} \ No newline at end of file diff --git a/lib/Plugins/src/VNLib.Plugins.csproj b/lib/Plugins/src/VNLib.Plugins.csproj index 5b05caf..51cdc20 100644 --- a/lib/Plugins/src/VNLib.Plugins.csproj +++ b/lib/Plugins/src/VNLib.Plugins.csproj @@ -48,8 +48,4 @@ - - - - diff --git a/lib/Plugins/src/VfReturnType.cs b/lib/Plugins/src/VfReturnType.cs deleted file mode 100644 index 8ebcb26..0000000 --- a/lib/Plugins/src/VfReturnType.cs +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: Interfaces.cs -* -* Interfaces.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -namespace VNLib.Plugins -{ - - /// - /// Represents the result of a virutal endpoint processing operation - /// - public enum VfReturnType - { - /// - /// Signals that the virtual endpoint - /// - ProcessAsFile, - /// - /// Signals that the virtual endpoint generated a response, and - /// the connection should be completed - /// - VirtualSkip, - /// - /// Signals that the virtual endpoint determined that the connection - /// should be denied. - /// - Forbidden, - /// - /// Signals that the resource the virtual endpoint was processing - /// does not exist. - /// - NotFound, - /// - /// Signals that the virutal endpoint determined the request was invalid - /// - BadRequest, - /// - /// Signals that the virtual endpoint had an error - /// - Error - } -} diff --git a/lib/Plugins/src/Web/IEndpoint.cs b/lib/Plugins/src/Web/IEndpoint.cs new file mode 100644 index 0000000..33d49df --- /dev/null +++ b/lib/Plugins/src/Web/IEndpoint.cs @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: IEndpoint.cs +* +* IEndpoint.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. +*/ + +namespace VNLib.Plugins +{ + /// + /// A base class for all entity processing endpoints to listen for requests + /// + public interface IEndpoint + { + /// + /// The location path for which to match this handler + /// + public string Path { get; } + } +} \ No newline at end of file diff --git a/lib/Plugins/src/Web/IWebPlugin.cs b/lib/Plugins/src/Web/IWebPlugin.cs new file mode 100644 index 0000000..ed080d0 --- /dev/null +++ b/lib/Plugins/src/Web/IWebPlugin.cs @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: IWebPlugin.cs +* +* IWebPlugin.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. +*/ + +using System.Collections.Generic; + +namespace VNLib.Plugins +{ + /// + /// Represents a plugin that is expected to perform web application based operations + /// + public interface IWebPlugin : IPlugin + { + /// + /// Returns all endpoints within the plugin to load into the current root + /// + /// An enumeration of endpoints to load + /// + /// Lifecycle: Results returned from this method should be consistant (although its only + /// likely to be called once) anytime after the method, and undefined + /// after the method is called. + /// + IEnumerable GetEndpoints(); + } +} \ No newline at end of file diff --git a/lib/Plugins/src/Web/WebMessage.cs b/lib/Plugins/src/Web/WebMessage.cs new file mode 100644 index 0000000..fb6ca6f --- /dev/null +++ b/lib/Plugins/src/Web/WebMessage.cs @@ -0,0 +1,47 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: WebMessage.cs +* +* WebMessage.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. +*/ + +using System.Text.Json.Serialization; + +namespace VNLib.Plugins +{ + public class WebMessage + { + /// + /// The encrypted access token for the client to use after a login request + /// + [JsonPropertyName("token")] + public string? Token { get; set; } + /// + /// The result of the REST operation to send to client + /// + [JsonPropertyName("result")] + public object? Result { get; set; } + /// + /// A status flag/result of the REST operation + /// + [JsonPropertyName("success")] + public bool Success { get; set; } + } +} diff --git a/lib/Plugins/src/WebMessage.cs b/lib/Plugins/src/WebMessage.cs deleted file mode 100644 index fb6ca6f..0000000 --- a/lib/Plugins/src/WebMessage.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: WebMessage.cs -* -* WebMessage.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -using System.Text.Json.Serialization; - -namespace VNLib.Plugins -{ - public class WebMessage - { - /// - /// The encrypted access token for the client to use after a login request - /// - [JsonPropertyName("token")] - public string? Token { get; set; } - /// - /// The result of the REST operation to send to client - /// - [JsonPropertyName("result")] - public object? Result { get; set; } - /// - /// A status flag/result of the REST operation - /// - [JsonPropertyName("success")] - public bool Success { get; set; } - } -} -- cgit