aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Hashing.Portable/src/Argon2/VnArgon2.cs2
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs10
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs21
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs75
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs (renamed from lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs)7
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs43
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs19
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginLoadConfiguration.cs62
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs2
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs89
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs60
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs193
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs49
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs2
-rw-r--r--lib/Plugins.Essentials/src/Accounts/AccountUtils.cs2
-rw-r--r--lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs (renamed from lib/Plugins/src/IVirtualEndpoint.cs)12
-rw-r--r--lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs (renamed from lib/Plugins/src/VfReturnType.cs)26
-rw-r--r--lib/Plugins.Essentials/src/EventProcessor.cs1
-rw-r--r--lib/Plugins.Essentials/src/IVirtualEndpointTable.cs2
-rw-r--r--lib/Plugins.Essentials/src/SemiConsistentVeTable.cs1
-rw-r--r--lib/Plugins.PluginBase/src/PluginBase.cs21
-rw-r--r--lib/Plugins.Runtime/src/IAssemblyLoader.cs51
-rw-r--r--lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs22
-rw-r--r--lib/Plugins.Runtime/src/IPluginConfig.cs7
-rw-r--r--lib/Plugins.Runtime/src/IPluginDiscoveryManager.cs39
-rw-r--r--lib/Plugins.Runtime/src/IPluginStack.cs47
-rw-r--r--lib/Plugins.Runtime/src/LivePlugin.cs25
-rw-r--r--lib/Plugins.Runtime/src/LoaderExtensions.cs222
-rw-r--r--lib/Plugins.Runtime/src/PluginController.cs6
-rw-r--r--lib/Plugins.Runtime/src/PluginStackBuilder.cs278
-rw-r--r--lib/Plugins.Runtime/src/RuntimePluginLoader.cs83
-rw-r--r--lib/Plugins/src/IPlugin.cs15
-rw-r--r--lib/Plugins/src/VNLib.Plugins.csproj4
-rw-r--r--lib/Plugins/src/Web/IEndpoint.cs (renamed from lib/Plugins/src/IEndpoint.cs)0
-rw-r--r--lib/Plugins/src/Web/IWebPlugin.cs45
-rw-r--r--lib/Plugins/src/Web/WebMessage.cs (renamed from lib/Plugins/src/WebMessage.cs)0
36 files changed, 983 insertions, 560 deletions
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<IHttpServer> _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<IHttpServer> Servers => _servers;
/// <summary>
- /// The service domain's plugin controller
+ /// Gets the internal <see cref="IHttpPluginManager"/> that manages plugins for the entire
+ /// <see cref="HttpServiceStack"/>
/// </summary>
- public IPluginManager PluginManager => _serviceDomain.PluginManager;
+ public IHttpPluginManager PluginManager => _plugins;
/// <summary>
/// Initializes a new <see cref="HttpServiceStack"/> that will
/// generate servers to listen for services exposed by the
/// specified host context
/// </summary>
- internal HttpServiceStack(LinkedList<IHttpServer> servers, ServiceDomain serviceDomain)
+ internal HttpServiceStack(LinkedList<IHttpServer> 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
}
/// <summary>
- /// 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
/// </summary>
/// <returns>The task that completes when</returns>
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
/// </summary>
public HttpServiceStackBuilder()
- {}
+ { }
private Action<ICollection<IServiceHost>>? _hostBuilder;
private Func<ServiceGroup, IHttpServer>? _getServers;
+ private Func<IPluginStack>? _getPlugins;
/// <summary>
/// Uses the supplied callback to get a collection of virtual hosts
/// to build the current domain with
/// </summary>
/// <param name="hostBuilder">The callback method to build virtual hosts</param>
- /// <returns>A value that indicates if any virtual hosts were successfully loaded</returns>
+ /// <returns>The current instance for chaining</returns>
public HttpServiceStackBuilder WithDomainBuilder(Action<ICollection<IServiceHost>> hostBuilder)
{
_hostBuilder = hostBuilder;
@@ -62,6 +64,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// Spcifies a callback function that builds <see cref="IHttpServer"/> instances from the hosts
/// </summary>
/// <param name="getServers">A callback method that gets the http server implementation for the service group</param>
+ /// <returns>The current instance for chaining</returns>
public HttpServiceStackBuilder WithHttp(Func<ServiceGroup, IHttpServer> getServers)
{
_getServers = getServers;
@@ -69,6 +72,17 @@ namespace VNLib.Plugins.Essentials.ServiceStack
}
/// <summary>
+ /// Enables the stack to support plugins
+ /// </summary>
+ /// <param name="getStack">The callback function that returns the plugin stack when requested</param>
+ /// <returns>The current instance for chaining</returns>
+ public HttpServiceStackBuilder WithPluginStack(Func<IPluginStack> getStack)
+ {
+ _getPlugins = getStack;
+ return this;
+ }
+
+ /// <summary>
/// Builds the new <see cref="HttpServiceStack"/> from the configured callbacks, WITHOUT loading plugins
/// </summary>
/// <returns>The newly constructed <see cref="HttpServiceStack"/> that may be used to manage your http services</returns>
@@ -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<IHttpServer> 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<IHttpServer> 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<RuntimePluginLoader> Plugins { get; } = Array.Empty<RuntimePluginLoader>();
+
+ public void BuildStack()
+ { }
+
+ public void Dispose()
+ { }
}
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs
index 2e686ee..c883e29 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs
@@ -3,9 +3,9 @@
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
-* File: IPluginWrapper.cs
+* File: IManagedPlugin.cs
*
-* IPluginWrapper.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger
+* 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
@@ -27,8 +27,9 @@ using VNLib.Plugins.Runtime;
namespace VNLib.Plugins.Essentials.ServiceStack
{
+
/// <summary>
- /// Represents a plugin managed by a <see cref="IPluginManager"/> that includes dynamically loaded plugins
+ /// Represents a plugin managed by a <see cref="IHttpPluginManager"/> that includes dynamically loaded plugins
/// </summary>
public interface IManagedPlugin
{
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
-{
- /// <summary>
- /// Represents a provider that creates a unique <see cref="IPluginAssemblyLoader"/> for the the
- /// loaded assembly file.
- /// </summary>
- public interface IPluginAssemblyLoaderFactory
- {
- /// <summary>
- /// Gets a new and unique <see cref="IPluginAssemblyLoader"/> instance for a given plugin assembly
- /// file path
- /// </summary>
- /// <param name="pluginFile">The file path to the requested plugin assembly file</param>
- /// <returns>The new <see cref="IPluginAssemblyLoader"/> to recover the plugin assembly manifest</returns>
- 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,31 +25,20 @@
using System;
using System.Collections.Generic;
-using VNLib.Utils.Logging;
-
namespace VNLib.Plugins.Essentials.ServiceStack
{
/// <summary>
/// Represents a live plugin controller that manages all
/// plugins loaded in a <see cref="ServiceDomain"/>
/// </summary>
- public interface IPluginManager
+ public interface IHttpPluginManager
{
/// <summary>
- /// The the plugins managed by this <see cref="IPluginManager"/>
+ /// The the plugins managed by this <see cref="IHttpPluginManager"/>
/// </summary>
public IEnumerable<IManagedPlugin> Plugins { get; }
/// <summary>
- /// Loads all plugins specified by the host config to the service manager,
- /// or attempts to load plugins by the default
- /// </summary>
- /// <param name="config">The plugin loading configuration</param>
- /// <param name="appLog">A log provider to write message and errors to</param>
- /// <returns>A task that resolves when all plugins are loaded</returns>
- void LoadPlugins(IPluginLoadConfiguration config, ILogProvider appLog);
-
- /// <summary>
/// Sends a message to a plugin identified by it's name.
/// </summary>
/// <param name="pluginName">The name of the plugin to pass the message to</param>
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
-{
-
- /// <summary>
- /// Plugin loading configuration variables
- /// </summary>
- public interface IPluginLoadConfiguration
- {
- /// <summary>
- /// The directory containing the dynamic plugin assemblies to load
- /// </summary>
- string PluginDir { get; }
-
- /// <summary>
- /// The optional host configuration file to merge with plugin config
- /// to pass to the loading plugin.
- /// </summary>
- JsonElement? HostConfig { get; }
-
- /// <summary>
- /// Passed to the underlying <see cref="RuntimePluginLoader"/>
- /// holding plugins
- /// </summary>
- ILogProvider? PluginErrorLog { get; }
-
- /// <summary>
- /// A factory instance the provides new <see cref="IPluginAssemblyLoader"/> instances
- /// on demand from its plugin assembly path
- /// </summary>
- public IPluginAssemblyLoaderFactory AssemblyLoaderFactory { 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);
/// <summary>
- /// Called when a <see cref="ServiceDomain"/>'s <see cref="IPluginManager"/>
+ /// Called when a <see cref="ServiceDomain"/>'s <see cref="IHttpPluginManager"/>
/// unloads a given plugin, and its originally discovered endpoints
/// </summary>
/// <param name="plugin">The unloading plugin to detach</param>
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;
- }
+ ///<inheritdoc/>
+ public string PluginPath => Plugin.Config.AssemblyFile;
+ private UnloadableServiceContainer? _services;
- ///<inheritdoc/>
- public string PluginPath => _plugin.Config.AssemblyFile;
+ public ManagedPlugin(RuntimePluginLoader loader) => Plugin = loader;
///<inheritdoc/>
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
+{
+ /// <summary>
+ /// Internal and service stack specific extensions for plugins
+ /// </summary>
+ public static class PluginExtensions
+ {
+ /// <summary>
+ /// Gets the endpoints exposed by the plugin
+ /// </summary>
+ /// <param name="plugin"></param>
+ /// <returns>The enumeration of web endpoints</returns>
+ internal static IEnumerable<IEndpoint> GetEndpoints(this IPlugin plugin) => ((IWebPlugin)plugin).GetEndpoints();
+
+ /// <summary>
+ /// Gets only plugins that implement <see cref="IWebPlugin"/> interface
+ /// </summary>
+ /// <param name="controller"></param>
+ /// <returns></returns>
+ internal static IEnumerable<LivePlugin> GetOnlyWebPlugins(this PluginController controller) => controller.Plugins.Where(p => p.Plugin is IWebPlugin);
+
+ /// <summary>
+ /// Loads all plugins that implement <see cref="IWebPlugin"/> interface into the
+ /// service stack
+ /// </summary>
+ /// <param name="stack"></param>
+ /// <param name="logProvider">A log provider for writing loading logs to</param>
+ 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.
/// </summary>
- internal sealed class PluginManager : VnDisposeable, IPluginManager, IPluginEventListener
+ internal sealed class PluginManager : VnDisposeable, IHttpPluginManager, IPluginEventListener
{
- private const string PLUGIN_FILE_EXTENSION = ".dll";
+ private readonly ConditionalWeakTable<PluginController, ManagedPlugin> _managedPlugins;
+ private readonly ServiceDomain _dependents;
+ private readonly IPluginStack _stack;
- private readonly List<ManagedPlugin> _plugins;
- private readonly IReadOnlyCollection<ServiceGroup> _dependents;
-
-
- private IEnumerable<LivePlugin> _livePlugins => _plugins.SelectMany(static p => p.Controller.Plugins);
+ private IEnumerable<LivePlugin> _livePlugins => _managedPlugins.SelectMany(static p => p.Key.Plugins);
/// <summary>
/// The collection of internal controllers
/// </summary>
- public IEnumerable<IManagedPlugin> Plugins => _plugins;
+ public IEnumerable<IManagedPlugin> Plugins => _managedPlugins.Select(static p => p.Value);
- public PluginManager(IReadOnlyCollection<ServiceGroup> dependents)
+ public PluginManager(ServiceDomain dependents, IPluginStack stack)
{
- _plugins = new();
_dependents = dependents;
+ _stack = stack;
+ _managedPlugins = new();
}
- /// <inheritdoc/>
- /// <exception cref="ObjectDisposedException"></exception>
- public void LoadPlugins(IPluginLoadConfiguration config, ILogProvider appLog)
+ /// <summary>
+ /// Configures the manager to capture and manage plugins within a plugin stack
+ /// </summary>
+ /// <param name="debugLog"></param>
+ 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<DirectoryInfo> dirs = dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly);
-
- //Select only dirs with a dll that is named after the directory name
- IEnumerable<string> pluginPaths = GetPluginPaths(dirs);
+ _ = _stack ?? throw new InvalidOperationException("Plugin stack has not been set.");
- IEnumerable<string> 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<string> GetPluginPaths(IEnumerable<DirectoryInfo> 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
/// <inheritdoc/>
public void ForceReloadAllPlugins()
{
- //Reload all plugin managers
- _plugins.TryForeach(static p => p.ReloadPlugins());
+ Check();
+
+ //Reload all plugins, causing an event cascade
+ _stack.ReloadAll();
}
/// <inheritdoc/>
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
/// </summary>
- public sealed class ServiceDomain : VnDisposeable
+ public sealed class ServiceDomain
{
private readonly LinkedList<ServiceGroup> _serviceGroups;
- private readonly PluginManager _plugins;
-
+
/// <summary>
/// Gets all service groups loaded in the service manager
/// </summary>
public IReadOnlyCollection<ServiceGroup> ServiceGroups => _serviceGroups;
/// <summary>
- /// Gets the internal <see cref="IPluginManager"/> that manages plugins for the entire
- /// <see cref="ServiceDomain"/>
- /// </summary>
- public IPluginManager PluginManager => _plugins;
-
- /// <summary>
/// Initializes a new empty <see cref="ServiceDomain"/>
/// </summary>
- public ServiceDomain()
- {
- _serviceGroups = new();
- //Init plugin manager and pass ref to service group collection
- _plugins = new PluginManager(_serviceGroups);
- }
+ public ServiceDomain() => _serviceGroups = new();
/// <summary>
/// Uses the supplied callback to get a collection of virtual hosts
@@ -69,11 +56,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// </summary>
/// <param name="hostBuilder">The callback method to build virtual hosts</param>
/// <returns>A value that indicates if any virtual hosts were successfully loaded</returns>
- /// <exception cref="ObjectDisposedException"></exception>
public bool BuildDomain(Action<ICollection<IServiceHost>> hostBuilder)
{
- Check();
-
//LL to store created hosts
LinkedList<IServiceHost> hosts = new();
@@ -88,11 +72,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// </summary>
/// <param name="hosts">The enumeration of virtual hosts</param>
/// <returns>A value that indicates if any virtual hosts were successfully loaded</returns>
- /// <exception cref="ObjectDisposedException"></exception>
public bool FromExisting(IEnumerable<IServiceHost> 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
}
/// <summary>
- /// Tears down the service domain by unloading all plugins (calling their event handlers)
- /// and destroying all <see cref="ServiceGroup"/>s. This instance may be rebuilt if this
- /// method returns successfully.
+ /// Tears down the service domain by destroying all <see cref="ServiceGroup"/>s. This instance may be rebuilt
+ /// if this method returns successfully.
/// </summary>
- 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();
}
-
-
- ///<inheritdoc/>
- 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
/// <param name="session"></param>
/// <param name="value">True for a local account, false otherwise</param>
[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!;
/// <summary>
/// Gets a value indicating if the session belongs to a local user account
diff --git a/lib/Plugins/src/IVirtualEndpoint.cs b/lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs
index 5f33c0f..10545cf 100644
--- a/lib/Plugins/src/IVirtualEndpoint.cs
+++ b/lib/Plugins.Essentials/src/Endpoints/IVirtualEndpoint.cs
@@ -1,19 +1,19 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins
+* Package: VNLib.Plugins.Essentials
* File: IVirtualEndpoint.cs
*
-* IVirtualEndpoint.cs is part of VNLib.Plugins which is part of the larger
+* IVirtualEndpoint.cs is part of VNLib.Plugins.Essentials which is part of the larger
* VNLib collection of libraries and utilities.
*
-* VNLib.Plugins is free software: you can redistribute it and/or modify
+* 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 is distributed in the hope that it will be useful,
+* 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.
@@ -24,7 +24,7 @@
using System.Threading.Tasks;
-namespace VNLib.Plugins
+namespace VNLib.Plugins.Essentials.Endpoints
{
/// <summary>
diff --git a/lib/Plugins/src/VfReturnType.cs b/lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs
index 8ebcb26..2fe29c7 100644
--- a/lib/Plugins/src/VfReturnType.cs
+++ b/lib/Plugins.Essentials/src/Endpoints/VfReturnType.cs
@@ -1,28 +1,28 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins
-* File: Interfaces.cs
+* Package: VNLib.Plugins.Essentials
+* File: VfReturnType.cs
*
-* Interfaces.cs is part of VNLib.Plugins which is part of the larger
+* VfReturnType.cs is part of VNLib.Plugins.Essentials which is part of the larger
* VNLib collection of libraries and utilities.
*
-* VNLib.Plugins is free software: you can redistribute it and/or modify
+* 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 is distributed in the hope that it will be useful,
+* 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/.
+* along with VNLib.Plugins.Essentials. If not, see http://www.gnu.org/licenses/.
*/
-namespace VNLib.Plugins
+namespace VNLib.Plugins.Essentials
{
/// <summary>
@@ -33,26 +33,26 @@ namespace VNLib.Plugins
/// <summary>
/// Signals that the virtual endpoint
/// </summary>
- ProcessAsFile,
+ ProcessAsFile,
/// <summary>
/// Signals that the virtual endpoint generated a response, and
/// the connection should be completed
/// </summary>
- VirtualSkip,
+ VirtualSkip,
/// <summary>
/// Signals that the virtual endpoint determined that the connection
/// should be denied.
/// </summary>
- Forbidden,
+ Forbidden,
/// <summary>
/// Signals that the resource the virtual endpoint was processing
/// does not exist.
/// </summary>
- NotFound,
+ NotFound,
/// <summary>
/// Signals that the virutal endpoint determined the request was invalid
/// </summary>
- BadRequest,
+ BadRequest,
/// <summary>
/// Signals that the virtual endpoint had an error
/// </summary>
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 <see cref="IPlugin"/> instances using the Serilog logging provider.
/// Accepts the standard plugin <see cref="JsonDocument"/> configuration constructors
/// </summary>
- 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
@@ -66,8 +66,14 @@ namespace VNLib.Plugins
protected virtual string GlobalConfigDomPropertyName => "host";
/// <summary>
+ /// The property name of the plugin configuration element in the plugin
+ /// runtime supplied configuration object.
+ /// </summary>
+ protected virtual string PluginConfigDomPropertyName => "plugin";
+
+ /// <summary>
/// A list of all currently prepared <see cref="IEndpoint"/> endpoints.
- /// Endpoints must be added to this list before <see cref="IPlugin.GetEndpoints"/> is called
+ /// Endpoints must be added to this list before <see cref="IWebPlugin.GetEndpoints"/> is called
/// by the host app
/// </summary>
public ICollection<IEndpoint> Endpoints { get; } = new List<IEndpoint>();
@@ -90,7 +96,7 @@ namespace VNLib.Plugins
/// <summary>
/// The configuration data from the plugin's config file passed by the host application
/// </summary>
- public JsonElement PluginConfig => Configuration.RootElement.GetProperty(GetType().Name);
+ public JsonElement PluginConfig => Configuration.RootElement.GetProperty(PluginConfigDomPropertyName);
/// <inheritdoc/>
public abstract string PluginName { get; }
@@ -252,7 +258,7 @@ namespace VNLib.Plugins
/// <param name="cmd">The command message</param>
protected abstract void ProcessHostCommand(string cmd);
- IEnumerable<IEndpoint> IPlugin.GetEndpoints()
+ IEnumerable<IEndpoint> 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();
/// <summary>
- /// Invoked before <see cref="IPlugin.GetEndpoints"/> called by the host app to get all endpoints
+ /// Invoked before <see cref="IWebPlugin.GetEndpoints"/> called by the host app to get all endpoints
/// for the current plugin
/// </summary>
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
+{
+ /// <summary>
+ /// Represents the core assembly loading functionality
+ /// </summary>
+ public interface IAssemblyLoader : IDisposable
+ {
+ /// <summary>
+ /// Unloads the assembly loader if Config.Unloadable is true, otherwise does nothing
+ /// </summary>
+ void Unload();
+
+ /// <summary>
+ /// Prepares the loader for use
+ /// </summary>
+ void Load();
+
+ /// <summary>
+ /// Begins the loading process and recovers the default assembly
+ /// </summary>
+ /// <returns>The main assembly from the assembly file</returns>
+ 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
{
+
/// <summary>
/// Represents the bare assembly loader that gets a main assembly for a plugin and handles
/// type resolution, while providing loading/unloading
/// </summary>
- public interface IPluginAssemblyLoader : IDisposable
+ public interface IPluginAssemblyLoader : IAssemblyLoader
{
/// <summary>
/// Gets the plugin's configuration information
/// </summary>
IPluginConfig Config { get; }
-
- /// <summary>
- /// Unloads the assembly loader if Config.Unloadable is true, otherwise does nothing
- /// </summary>
- void Unload();
-
- /// <summary>
- /// Prepares the loader for use
- /// </summary>
- void Load();
-
- /// <summary>
- /// Begins the loading process and recovers the default assembly
- /// </summary>
- /// <returns>The main assembly from the assembly file</returns>
- 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
/// </summary>
TimeSpan ReloadDelay { get; }
+
+ /// <summary>
+ /// Reads the host configuration into the given stream
+ /// </summary>
+ /// <param name="outputStream">The stream to write configurationd data to</param>
+ 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
+{
+ /// <summary>
+ /// Represents a plugin discovery manager that, when requestesd, discovers
+ /// all plugin assembly files.
+ /// </summary>
+ public interface IPluginDiscoveryManager
+ {
+ /// <summary>
+ /// Gets all plugin assembly files that should be loaded
+ /// </summary>
+ /// <returns></returns>
+ 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
+{
+ /// <summary>
+ /// Provides a container and functionality to manage an entire collection
+ /// of plugins.
+ /// </summary>
+ public interface IPluginStack : IDisposable
+ {
+ /// <summary>
+ /// The collection of all plugin loaders
+ /// </summary>
+ IReadOnlyCollection<RuntimePluginLoader> Plugins { get; }
+
+ /// <summary>
+ /// Discovers all plugins for the runtime and populates
+ /// the <see cref="Plugins"/> collection.
+ /// </summary>
+ 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 <see cref="ConfigurationInitalizerAttribute"/>
/// on an instance method
/// </summary>
- /// <param name="hostConfig">The host configuration DOM</param>
- /// <param name="pluginConf">The plugin local configuration DOM</param>
- internal void InitConfig(JsonDocument hostConfig, JsonDocument pluginConf)
+ /// <param name="configData">The host configuration DOM</param>
+ internal void InitConfig(ReadOnlySpan<byte> configData)
{
//Get the console handler method from the plugin instance
MethodInfo? confHan = PluginType.GetMethods().Where(static m => m.GetCustomAttribute<ConfigurationInitalizerAttribute>() != 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);
}
/// <summary>
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);
}
-
- /// <summary>
- /// Loads the configuration file into its <see cref="JsonDocument"/> format
- /// for reading.
- /// </summary>
- /// <param name="loader"></param>
- /// <returns>A new <see cref="JsonDocument"/> of the loaded configuration file</returns>
- 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);
- }
-
+
/// <summary>
/// Determines if the current <see cref="PluginController"/>
/// exposes the desired type on is <see cref="IPlugin"/>
@@ -165,5 +147,201 @@ namespace VNLib.Plugins.Runtime
return plugin?.Plugin as T;
}
+
+ /// <summary>
+ /// Serially initialzies all plugin lifecycle controllers and configures
+ /// plugin instances.
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void InitializeAll(this IPluginStack runtime)
+ {
+ _ = runtime ?? throw new ArgumentNullException(nameof(runtime));
+
+ foreach(RuntimePluginLoader loader in runtime.Plugins)
+ {
+ loader.InitializeController();
+ }
+ }
+
+ /// <summary>
+ /// Invokes the load method for all plugin instances
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="AggregateException"></exception>
+ public static void InvokeLoad(this IPluginStack runtime)
+ {
+ _ = runtime ?? throw new ArgumentNullException(nameof(runtime));
+
+ //try loading all plugins
+ runtime.Plugins.TryForeach(static p => p.LoadPlugins());
+ }
+
+ /// <summary>
+ /// Invokes the unload method for all plugin instances
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="AggregateException"></exception>
+ public static void InvokeUnload(this IPluginStack runtime)
+ {
+ _ = runtime ?? throw new ArgumentNullException(nameof(runtime));
+
+ //try unloading all plugins
+ runtime.Plugins.TryForeach(static p => p.UnloadPlugins());
+ }
+
+ /// <summary>
+ /// Unloads all plugins and the plugin assembly loader
+ /// if unloading is supported.
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="AggregateException"></exception>
+ 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());
+ }
+
+ /// <summary>
+ /// Reloads all plugins and each assembly loader
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="AggregateException"></exception>
+ public static void ReloadAll(this IPluginStack runtime)
+ {
+ _ = runtime ?? throw new ArgumentNullException(nameof(runtime));
+
+ //try reloading all plugins
+ runtime.Plugins.TryForeach(static p => p.ReloadPlugins());
+ }
+
+ /// <summary>
+ /// Registers a plugin event listener for all plugins
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <param name="listener">The event listener instance</param>
+ /// <param name="state">Optional state parameter</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Unregisters a plugin event listener for all plugins
+ /// </summary>
+ /// <param name="runtime"></param>
+ /// <param name="listener">The listener instance to unregister</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Specify the host configuration data to pass to the plugin
+ /// </summary>
+ /// <param name="builder"></param>
+ /// <param name="hostConfig">A configuration element to pass to the plugin's host config element</param>
+ /// <returns>The current builder instance for chaining</returns>
+ 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());
+ }
+
+ /// <summary>
+ /// Specifies the directory that the plugin loader will search for plugins in
+ /// </summary>
+ /// <param name="path">The search directory path</param>
+ /// <param name="builder"></param>
+ /// <returns>The current builder instance for chaining</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static PluginStackBuilder WithSearchDirectory(this PluginStackBuilder builder, string path) => WithSearchDirectory(builder, new DirectoryInfo(path));
+
+ /// <summary>
+ /// Specifies the directory that the plugin loader will search for plugins in
+ /// </summary>
+ /// <param name="dir">The search directory instance</param>
+ /// <param name="builder"></param>
+ /// <returns>The current builder instance for chaining</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Gets the current collection of loaded plugins for the plugin stack
+ /// </summary>
+ /// <param name="stack"></param>
+ /// <returns>An enumeration of all <see cref="LivePlugin"/> wrappers</returns>
+ public static IEnumerable<LivePlugin> 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";
+
+ ///<inheritdoc/>
+ public string[] DiscoverPluginFiles()
+ {
+ //Enumerate all dll files within the seach directory
+ IEnumerable<DirectoryInfo> dirs = Dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly);
+
+ //Search all directories for plugins and return the paths
+ return GetPluginPaths(dirs).ToArray();
+ }
+
+ private static IEnumerable<string> GetPluginPaths(IEnumerable<DirectoryInfo> 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
+{
+ /// <summary>
+ /// A construction class used to build a single plugin stack.
+ /// </summary>
+ public sealed class PluginStackBuilder
+ {
+ private IPluginDiscoveryManager? DiscoveryManager;
+ private bool HotReload;
+ private TimeSpan ReloadDelay;
+ private byte[]? HostConfigData;
+ private ILogProvider? DebugLog;
+
+ private Func<IPluginConfig, IAssemblyLoader>? Loader;
+
+ public static PluginStackBuilder Create() => new();
+
+ /// <summary>
+ /// Sets the plugin discovery manager used to find plugins
+ /// </summary>
+ /// <param name="discoveryManager">The discovery manager instance</param>
+ /// <returns>The current builder instance for chaining</returns>
+ public PluginStackBuilder WithDiscoveryManager(IPluginDiscoveryManager discoveryManager)
+ {
+ DiscoveryManager = discoveryManager;
+ return this;
+ }
+
+ /// <summary>
+ /// Enables hot reloading of the plugin assembly
+ /// </summary>
+ /// <param name="reloadDelay">The delay time after a change is detected before the assembly is reloaded</param>
+ /// <returns>The current builder instance for chaining</returns>
+ public PluginStackBuilder EnableHotReload(TimeSpan reloadDelay)
+ {
+ HotReload = true;
+ ReloadDelay = reloadDelay;
+ return this;
+ }
+
+ /// <summary>
+ /// Specifies the JSON host configuration data to pass to the plugin
+ /// </summary>
+ /// <param name="hostConfig"></param>
+ /// <returns>The current builder instance for chaining</returns>
+ public PluginStackBuilder WithConfigurationData(ReadOnlySpan<byte> hostConfig)
+ {
+ //Store binary copy
+ HostConfigData = hostConfig.ToArray();
+ return this;
+ }
+
+ /// <summary>
+ /// The factory callback function used to get assembly loaders for
+ /// discovered plugins
+ /// </summary>
+ /// <param name="loaderFactory">The factory callback funtion</param>
+ /// <returns>The current builder instance for chaining</returns>
+ public PluginStackBuilder WithLoaderFactory(Func<IPluginConfig, IAssemblyLoader> loaderFactory)
+ {
+ Loader = loaderFactory;
+ return this;
+ }
+
+ /// <summary>
+ /// Specifies the optional debug log provider to use for the plugin loader.
+ /// </summary>
+ /// <param name="logProvider">The optional log provider instance</param>
+ ///<returns>The current builder instance for chaining</returns>
+ public PluginStackBuilder WithDebugLog(ILogProvider logProvider)
+ {
+ DebugLog = logProvider;
+ return this;
+ }
+
+ /// <summary>
+ /// Creates a snapshot of the current builder state and builds a plugin stack
+ /// </summary>
+ /// <returns>The current builder instance for chaining</returns>
+ /// <exception cref="ArgumentException"></exception>
+ 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<RuntimePluginLoader> _plugins = new();
+
+ ///<inheritdoc/>
+ public IReadOnlyCollection<RuntimePluginLoader> Plugins => _plugins;
+
+ ///<inheritdoc/>
+ 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<string> pluginPaths = Builder.DiscoveryManager!.DiscoverPluginFiles();
+
+ //Log the found plugin files
+ IEnumerable<string> pluginFileNames = pluginPaths.Select(static s => $"{Path.GetFileName(s)}\n");
+ debugLog?.Debug("Found plugin assemblies: \n{files}", string.Concat(pluginFileNames));
+
+ LinkedList<IPluginAssemblyLoader> 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();
+ }
+
+
+ ///<inheritdoc/>
+ public void Dispose()
+ {
+ //dispose all plugins
+ _plugins.TryForeach(static p => p.Dispose());
+ _plugins.Clear();
+ }
+ }
+
+ internal sealed record class PluginAsmLoader(IAssemblyLoader Loader, IPluginConfig Config) : IPluginAssemblyLoader
+ {
+ ///<inheritdoc/>
+ public void Dispose() => Loader.Dispose();
+
+ ///<inheritdoc/>
+ public Assembly GetAssembly() => Loader.GetAssembly();
+
+ ///<inheritdoc/>
+ public void Load() => Loader.Load();
+
+ ///<inheritdoc/>
+ public void Unload() => Loader.Unload();
+ }
+
+ internal sealed record class PlugingAssemblyConfig(ReadOnlyMemory<byte> HostConfig) : IPluginConfig
+ {
+ ///<inheritdoc/>
+ public bool Unloadable { get; init; }
+
+ ///<inheritdoc/>
+ public string AssemblyFile { get; init; } = string.Empty;
+
+ ///<inheritdoc/>
+ public bool WatchForReload { get; init; }
+
+ ///<inheritdoc/>
+ 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");
+
+ ///<inheritdoc/>
+ 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;
/// <summary>
@@ -54,11 +52,6 @@ namespace VNLib.Plugins.Runtime
/// Gets the plugin lifecycle controller
/// </summary>
public PluginController Controller { get; }
-
- /// <summary>
- /// The path of the plugin's configuration file. (Default = pluginPath.json)
- /// </summary>
- public string PluginConfigPath => Path.ChangeExtension(Config.AssemblyFile, ".json");
/// <summary>
/// Creates a new <see cref="RuntimePluginLoader"/> with the specified config and host config dom.
@@ -67,14 +60,11 @@ namespace VNLib.Plugins.Runtime
/// <param name="hostConfig">The host/process configuration DOM</param>
/// <param name="log">A log provider to write plugin unload log events to</param>
/// <exception cref="ArgumentNullException"></exception>
- 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
/// <exception cref="FileNotFoundException"></exception>
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);
}
/// <summary>
@@ -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/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
{
/// <summary>
@@ -36,23 +33,15 @@ namespace VNLib.Plugins
/// The name of the plugin to referrence (may be used by the host to interact)
/// </summary>
string PluginName { get; }
+
/// <summary>
/// Performs operations to prepare the plugin for use
/// </summary>
void Load();
+
/// <summary>
/// Invoked when the plugin is unloaded from the runtime
/// </summary>
void Unload();
- /// <summary>
- /// Returns all endpoints within the plugin to load into the current root
- /// </summary>
- /// <returns>An enumeration of endpoints to load</returns>
- /// <remarks>
- /// Lifecycle: Results returned from this method should be consistant (although its only
- /// likely to be called once) anytime after the <see cref="Load"/> method, and undefined
- /// after the <see cref="Unload"/> method is called.
- /// </remarks>
- IEnumerable<IEndpoint> GetEndpoints();
}
} \ 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 @@
</None>
</ItemGroup>
- <ItemGroup>
- <Folder Include="Services\" />
- </ItemGroup>
-
</Project>
diff --git a/lib/Plugins/src/IEndpoint.cs b/lib/Plugins/src/Web/IEndpoint.cs
index 33d49df..33d49df 100644
--- a/lib/Plugins/src/IEndpoint.cs
+++ b/lib/Plugins/src/Web/IEndpoint.cs
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
+{
+ /// <summary>
+ /// Represents a plugin that is expected to perform web application based operations
+ /// </summary>
+ public interface IWebPlugin : IPlugin
+ {
+ /// <summary>
+ /// Returns all endpoints within the plugin to load into the current root
+ /// </summary>
+ /// <returns>An enumeration of endpoints to load</returns>
+ /// <remarks>
+ /// Lifecycle: Results returned from this method should be consistant (although its only
+ /// likely to be called once) anytime after the <see cref="IPlugin.Load"/> method, and undefined
+ /// after the <see cref="IPlugin.Unload"/> method is called.
+ /// </remarks>
+ IEnumerable<IEndpoint> GetEndpoints();
+ }
+} \ No newline at end of file
diff --git a/lib/Plugins/src/WebMessage.cs b/lib/Plugins/src/Web/WebMessage.cs
index fb6ca6f..fb6ca6f 100644
--- a/lib/Plugins/src/WebMessage.cs
+++ b/lib/Plugins/src/Web/WebMessage.cs