aboutsummaryrefslogtreecommitdiff
path: root/lib/Plugins.Essentials.ServiceStack
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-10-14 15:41:17 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-10-14 15:41:17 -0400
commit62f9e126912fa9a620a361fb5b88d33506e096fb (patch)
tree78665fe8516c559821aa4358ca9e2734e475415a /lib/Plugins.Essentials.ServiceStack
parent0f0c991891b6be076a9a367627201eceeb6d354e (diff)
some refactoring and tests
Diffstat (limited to 'lib/Plugins.Essentials.ServiceStack')
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs22
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs3
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs40
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs77
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs43
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs99
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs144
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs329
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs17
9 files changed, 529 insertions, 245 deletions
diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs
index 763eb26..c4d602b 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs
@@ -50,6 +50,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
private Action<ICollection<IServiceHost>>? _hostBuilder;
private Func<ServiceGroup, IHttpServer>? _getServers;
private Func<IPluginStack>? _getPlugins;
+ private IManualPlugin[]? manualPlugins;
/// <summary>
/// Uses the supplied callback to get a collection of virtual hosts
@@ -93,7 +94,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
/// <returns>The current instance for chaining</returns>
public HttpServiceStackBuilder WithBuiltInHttp(Func<ServiceGroup, ITransportProvider> transport, HttpConfig config)
{
- return WithHttp(sg => new HttpServer(config, transport(sg), sg.Hosts.Select(static p => p.Processor)));
+ return WithBuiltInHttp(transport, sg => config);
}
/// <summary>
@@ -108,6 +109,18 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
}
/// <summary>
+ /// Adds a collection of manual plugin instances to the stack. Every call
+ /// to this method will replace the previous collection.
+ /// </summary>
+ /// <param name="plugins">The array of plugins (or params) to add</param>
+ /// <returns>The current instance for chaining</returns>
+ public HttpServiceStackBuilder WithManualPlugins(params IManualPlugin[] plugins)
+ {
+ manualPlugins = plugins;
+ return this;
+ }
+
+ /// <summary>
/// Builds the new <see cref="HttpServiceStack"/> from the configured callbacks
/// </summary>
/// <returns>The newly constructed <see cref="HttpServiceStack"/> that may be used to manage your http services</returns>
@@ -137,6 +150,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
servers.AddLast(server);
}
+ //Always init manual array
+ manualPlugins ??= Array.Empty<IManualPlugin>();
+
//Only load plugins if the callback is configured
IPluginStack? plugins = _getPlugins?.Invoke();
@@ -144,7 +160,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
plugins ??= new EmptyPluginStack();
#pragma warning restore CA2000 // Dispose objects before losing scope
- return new(servers, sd, plugins);
+ IPluginInitializer init = new PluginStackInitializer(plugins, manualPlugins);
+
+ return new(servers, sd, init);
}
/*
diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
index 51c3091..7cd5ac4 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
@@ -29,7 +29,6 @@ using System.Collections.Generic;
using VNLib.Utils;
using VNLib.Net.Http;
-using VNLib.Plugins.Runtime;
namespace VNLib.Plugins.Essentials.ServiceStack
{
@@ -62,7 +61,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// generate servers to listen for services exposed by the
/// specified host context
/// </summary>
- internal HttpServiceStack(LinkedList<IHttpServer> servers, ServiceDomain serviceDomain, IPluginStack plugins)
+ internal HttpServiceStack(LinkedList<IHttpServer> servers, ServiceDomain serviceDomain, IPluginInitializer plugins)
{
_servers = servers;
_serviceDomain = serviceDomain;
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs
index 852bb95..21c8e91 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs
@@ -22,30 +22,17 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
using System.ComponentModel.Design;
-using VNLib.Plugins.Runtime;
-
namespace VNLib.Plugins.Essentials.ServiceStack
{
-
-
/// <summary>
/// Represents a plugin managed by a <see cref="IHttpPluginManager"/> that includes dynamically loaded plugins
/// </summary>
public interface IManagedPlugin
{
/// <summary>
- /// Exposes the internal <see cref="PluginController"/> for the loaded plugin
- /// </summary>
- PluginController Controller { get; }
-
- /// <summary>
- /// The file path to the loaded plugin
- /// </summary>
- string PluginPath { get; }
-
- /// <summary>
/// The exposed services the inernal plugin provides
/// </summary>
/// <remarks>
@@ -53,5 +40,30 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// must listen for plugin load/unload events to respect lifecycles properly.
/// </remarks>
IServiceContainer Services { get; }
+
+ /// <summary>
+ /// Internal call to get all exported plugin endpoints
+ /// </summary>
+ /// <returns></returns>
+ internal IEndpoint[] GetEndpoints();
+
+ /// <summary>
+ /// Internal notification that the plugin is loaded
+ /// </summary>
+ internal void OnPluginLoaded();
+
+ /// <summary>
+ /// Internal notification that the plugin is unloaded
+ /// </summary>
+ internal void OnPluginUnloaded();
+
+ /// <summary>
+ /// Sends the specified command to the desired plugin by it's name
+ /// </summary>
+ /// <param name="pluginName">The name of the plugin to find</param>
+ /// <param name="command">The command text to send to the plugin</param>
+ /// <param name="comp">The string name comparison type</param>
+ /// <returns>True if the command was sent successfully</returns>
+ internal bool SendCommandToPlugin(string pluginName, string command, StringComparison comp);
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs
new file mode 100644
index 0000000..e58067f
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs
@@ -0,0 +1,77 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: IManualPlugin.cs
+*
+* IManualPlugin.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.ComponentModel.Design;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+ /// <summary>
+ /// Represents a plugin that may be added to a service stack in user-code
+ /// instead of the conventional runtime plugin loading system
+ /// </summary>
+ public interface IManualPlugin : IDisposable
+ {
+ /// <summary>
+ /// The name of the plugin
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Collects all exported services for use within the service stack
+ /// </summary>
+ /// <param name="container">The container to add services to</param>
+ void GetAllExportedServices(IServiceContainer container);
+
+ /// <summary>
+ /// Collects all exported endpoints to put into service
+ /// </summary>
+ /// <returns>The collection of endpoints</returns>
+ IEndpoint[] GetEndpoints();
+
+ /// <summary>
+ /// Initializes the plugin, called before accessing any other methods
+ /// </summary>
+ void Initialize();
+
+ /// <summary>
+ /// Loads the plugin, called after initialization but before getting
+ /// endpoints or services to allow for the plugin to configure itself
+ /// and perform initial setup
+ /// </summary>
+ void Load();
+
+ /// <summary>
+ /// Called when an unload was requested, either manually by the plugin controller
+ /// or when the service stack is unloading
+ /// </summary>
+ void Unload();
+
+ /// <summary>
+ /// Passes a console command to the plugin
+ /// </summary>
+ /// <param name="command">The raw command text to pass to the plugin from the console</param>
+ void OnConsoleCommand(string command);
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs
new file mode 100644
index 0000000..ac91f45
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: IPluginInitializer.cs
+*
+* IPluginInitializer.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.Utils.Logging;
+using VNLib.Plugins.Runtime;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+ internal interface IPluginInitializer
+ {
+ void PrepareStack(IPluginEventListener listener);
+
+ IManagedPlugin[] InitializePluginStack(ILogProvider eventLogger);
+
+ void UnloadPlugins();
+
+ void ReloadPlugins();
+
+ void Dispose();
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs
deleted file mode 100644
index 0c0dfa5..0000000
--- a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.ServiceStack
-* File: ManagedPlugin.cs
-*
-* ManagedPlugin.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.Linq;
-using System.Reflection;
-using System.ComponentModel.Design;
-
-using VNLib.Plugins.Runtime;
-using VNLib.Plugins.Attributes;
-
-
-namespace VNLib.Plugins.Essentials.ServiceStack
-{
-
- internal sealed class ManagedPlugin : IManagedPlugin
- {
- internal RuntimePluginLoader Plugin { get; }
-
- ///<inheritdoc/>
- public string PluginPath => Plugin.Config.AssemblyFile;
-
- private ServiceContainer? _services;
-
- public ManagedPlugin(RuntimePluginLoader loader) => Plugin = loader;
-
- ///<inheritdoc/>
- public IServiceContainer Services
- {
- get
- {
- _ = _services ?? throw new InvalidOperationException("The service container is not currently loaded");
- return _services!;
- }
- }
-
- ///<inheritdoc/>
- public PluginController Controller => Plugin.Controller;
-
- /*
- * Automatically called after the plugin has successfully loaded
- * by event handlers below
- */
-
- internal void OnPluginLoaded()
- {
- //If the service container is defined, dispose
- _services?.Dispose();
-
- //Init new service container
- _services = new();
-
- //Get types from plugin
- foreach (LivePlugin plugin in Plugin.Controller.Plugins)
- {
- /*
- * Get the exposed configurator method if declared,
- * it may not be defined.
- */
- ServiceConfigurator? callback = plugin.PluginType.GetMethods()
- .Where(static m => m.GetCustomAttribute<ServiceConfiguratorAttribute>() != null && !m.IsAbstract)
- .Select(m => m.CreateDelegate<ServiceConfigurator>(plugin.Plugin))
- .FirstOrDefault();
-
- //Invoke if defined to expose services
- callback?.Invoke(_services);
- }
- }
-
- internal void OnPluginUnloaded()
- {
- //Cleanup services no longer in use. Plugin is still valid until this method returns
- _services?.Dispose();
- //Remove ref to services
- _services = null;
- }
- }
-}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs
index 476f3f8..2f57367 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs
@@ -24,10 +24,7 @@
using System;
-using System.IO;
using System.Linq;
-using System.Diagnostics;
-using System.Threading.Tasks;
using System.Collections.Generic;
using VNLib.Utils;
@@ -43,23 +40,22 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// instances, exposes controls, and relays stateful plugin events.
/// </summary>
internal sealed class PluginManager : VnDisposeable, IHttpPluginManager, IPluginEventListener
- {
- private readonly Dictionary<PluginController, ManagedPlugin> _managedPlugins;
+ {
private readonly ServiceDomain _dependents;
- private readonly IPluginStack _stack;
-
- private IEnumerable<LivePlugin> _livePlugins => _managedPlugins.SelectMany(static p => p.Key.Plugins);
+ private readonly IPluginInitializer _stack;
/// <summary>
/// The collection of internal controllers
/// </summary>
- public IEnumerable<IManagedPlugin> Plugins => _managedPlugins.Select(static p => p.Value);
+ public IEnumerable<IManagedPlugin> Plugins => _loadedPlugins;
+
+ private IManagedPlugin[] _loadedPlugins;
- public PluginManager(ServiceDomain dependents, IPluginStack stack)
+ public PluginManager(ServiceDomain dependents, IPluginInitializer stack)
{
_dependents = dependents;
_stack = stack;
- _managedPlugins = new();
+ _loadedPlugins = Array.Empty<IManagedPlugin>();
}
/// <summary>
@@ -70,104 +66,29 @@ namespace VNLib.Plugins.Essentials.ServiceStack
{
_ = _stack ?? throw new InvalidOperationException("Plugin stack has not been set.");
- /*
- * Since we own the plugin stack, it is safe to build it here.
- * This method is not public and should not be called more than
- * once. Otherwise it can cause issues with the plugin stack.
- */
- _stack.BuildStack();
-
- //Register for plugin events
- _stack.RegsiterListener(this, this);
-
- //Create plugin wrappers from loaded plugins
- ManagedPlugin[] wrapper = _stack.Plugins.Select(p => new ManagedPlugin(p)).ToArray();
+ _stack.PrepareStack(this);
- //Add all wrappers to the managed plugins table
- Array.ForEach(wrapper, w => _managedPlugins.Add(w.Plugin.Controller, w));
-
- //Init remaining controllers single-threaded because it may mutate the table
- _managedPlugins.Select(p => p.Value).TryForeach(w => InitializePlugin(w.Plugin, debugLog));
-
- //Load stage, load all multithreaded
- Parallel.ForEach(_managedPlugins.Values, wp => LoadPlugin(wp.Plugin, debugLog));
+ //Initialize the plugin stack and store the loaded plugins
+ _loadedPlugins = _stack.InitializePluginStack(debugLog);
debugLog.Information("Plugin loading completed");
}
- /*
- * Plugins are manually loaded by this manager instead of the stack shortcut extensions
- * because I want to catch individual exceptions.
- *
- * I do not prefer this method as I would prefer loading is handled by the stack
- * and the host not by this library.
- *
- * This will change in the future.
- */
-
- private void InitializePlugin(RuntimePluginLoader plugin, ILogProvider debugLog)
- {
- string fileName = Path.GetFileName(plugin.Config.AssemblyFile);
-
- try
- {
- //Initialzie plugin wrapper
- plugin.InitializeController();
-
- /*
- * 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 {asm} assembly. This may be due to an assebmly mismatch", fileName);
- }
- }
- catch (Exception 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);
- }
- }
-
- private static void LoadPlugin(RuntimePluginLoader plugin, ILogProvider debugLog)
- {
- string fileName = Path.GetFileName(plugin.Config.AssemblyFile);
-
- Stopwatch sw = new();
- try
- {
- sw.Start();
-
- //Load wrapper
- plugin.LoadPlugins();
-
- sw.Stop();
-
- debugLog.Verbose("Loaded {pl} in {tm} ms", fileName, sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", fileName, ex);
- }
- finally
- {
- sw.Stop();
- }
- }
/// <inheritdoc/>
public bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal)
{
Check();
- //Find the single plugin by its name
- LivePlugin? pl = _livePlugins.Where(p => pluginName.Equals(p.PluginName, nameComparison)).SingleOrDefault();
+ foreach(IManagedPlugin plugin in _loadedPlugins)
+ {
+ if(plugin.SendCommandToPlugin(pluginName, message, nameComparison))
+ {
+ return true;
+ }
+ }
- //Send the command
- return pl?.SendConsoleMessage(message) ?? false;
+ return false;
}
/// <inheritdoc/>
@@ -176,7 +97,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
Check();
//Reload all plugins, causing an event cascade
- _stack.ReloadAll();
+ _stack.ReloadPlugins();
}
/// <inheritdoc/>
@@ -185,7 +106,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
Check();
//Unload all plugin controllers
- _stack.UnloadAll();
+ _stack.UnloadPlugins();
/*
* All plugin instances must be destroyed because the
@@ -198,26 +119,15 @@ namespace VNLib.Plugins.Essentials.ServiceStack
protected override void Free()
{
//Clear plugin table
- _managedPlugins.Clear();
+ _loadedPlugins = Array.Empty<IManagedPlugin>();
//Dispose the plugin stack
_stack.Dispose();
}
- /*
- * When using a service stack an loading manually, plugins that have errors
- * will not be captured by this instance. However when using the shortcut
- * extensions, the events will be invoked regaldess if we loaded the plugin
- * here.
- */
-
void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state)
{
- //Make sure the plugin is managed by this manager
- if(!_managedPlugins.TryGetValue(controller, out ManagedPlugin? mp))
- {
- return;
- }
+ IManagedPlugin mp = (state as IManagedPlugin)!;
//Run onload method before invoking other handlers
mp.OnPluginLoaded();
@@ -231,11 +141,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state)
{
- //Make sure the plugin is managed by this manager
- if (!_managedPlugins.TryGetValue(controller, out ManagedPlugin? mp))
- {
- return;
- }
+ IManagedPlugin plugin = (state as IManagedPlugin)!;
try
{
@@ -243,12 +149,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack
ServiceGroup[] deps = _dependents.ServiceGroups.Select(static d => d).ToArray();
//Run unloaded method
- deps.TryForeach(d => d.OnPluginUnloaded(mp));
+ deps.TryForeach(d => d.OnPluginUnloaded(plugin));
}
finally
{
//always unload the plugin wrapper
- mp.OnPluginUnloaded();
+ plugin.OnPluginUnloaded();
}
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs
new file mode 100644
index 0000000..6ccd862
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs
@@ -0,0 +1,329 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: PluginStackInitializer.cs
+*
+* PluginStackInitializer.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.IO;
+using System.Linq;
+using System.Reflection;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.ComponentModel.Design;
+
+using VNLib.Utils.Logging;
+using VNLib.Plugins.Runtime;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins.Attributes;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+
+ internal sealed record class PluginStackInitializer(IPluginStack Stack, IManualPlugin[] ManualPlugins) : IPluginInitializer
+ {
+ private readonly LinkedList<IManagedPlugin> _managedPlugins = new();
+ private readonly LinkedList<ManualPluginWrapper> _manualPlugins = new();
+
+ ///<inheritdoc/>
+ public void PrepareStack(IPluginEventListener listener)
+ {
+ /*
+ * Since we own the plugin stack, it is safe to build it here.
+ * This method is not public and should not be called more than
+ * once. Otherwise it can cause issues with the plugin stack.
+ */
+ Stack.BuildStack();
+
+ //Create plugin wrappers from loaded plugins
+ ManagedPlugin[] wrapper = Stack.Plugins.Select(static p => new ManagedPlugin(p)).ToArray();
+
+ //Add wrappers to list of managed plugins
+ Array.ForEach(wrapper, p => _managedPlugins.AddLast(p));
+
+ //Register for all plugins and pass the plugin instance as the state object
+ Array.ForEach(wrapper, p => p.Plugin.Controller.Register(listener, p));
+
+ //Add manual plugins to list of managed plugins
+ Array.ForEach(ManualPlugins, p => _manualPlugins.AddLast(new ManualPluginWrapper(p)));
+ }
+
+ ///<inheritdoc/>
+ public IManagedPlugin[] InitializePluginStack(ILogProvider debugLog)
+ {
+ //single thread initialziation
+ LinkedList<IManagedPlugin> _loadedPlugins = new();
+
+ //Combine all managed plugins and initialize them individually
+ IEnumerable<IManagedPlugin> plugins = _managedPlugins.Union(_manualPlugins);
+
+ foreach(IManagedPlugin p in plugins)
+ {
+ //Try init plugin and add it to the list of loaded plugins
+ if (InitializePluginCore(p, debugLog))
+ {
+ _loadedPlugins.AddLast(p);
+ }
+ }
+
+ //Load stage, load only initialized plugins
+ Parallel.ForEach(_loadedPlugins, wp => LoadPlugin(wp, debugLog));
+
+ return _loadedPlugins.ToArray();
+ }
+
+ ///<inheritdoc/>
+ public void UnloadPlugins()
+ {
+ Stack.UnloadAll();
+ _manualPlugins.TryForeach(static mp => mp.Unload());
+ }
+
+ ///<inheritdoc/>
+ public void ReloadPlugins()
+ {
+ Stack.ReloadAll();
+
+ //Reload manual plugins
+ _manualPlugins.TryForeach(static mp => mp.Unload());
+ _manualPlugins.TryForeach(static mp => mp.Load());
+ }
+
+ ///<inheritdoc/>
+ public void Dispose()
+ {
+ Stack.Dispose();
+ _manualPlugins.TryForeach(static mp => mp.Dispose());
+ _manualPlugins.Clear();
+ }
+
+ private static bool InitializePluginCore(IManagedPlugin plugin, ILogProvider debugLog)
+ {
+ try
+ {
+ if (plugin is ManagedPlugin mp)
+ {
+ //Initialzie plugin wrapper
+ mp.Plugin.InitializeController();
+
+ /*
+ * 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 (!mp.Plugin.Controller.Plugins.Any())
+ {
+ debugLog.Warn("No plugin instances were exposed via {asm} assembly. This may be due to an assebmly mismatch", plugin.ToString());
+ }
+ }
+ else if(plugin is ManualPluginWrapper mpw)
+ {
+ //Initialzie plugin wrapper
+ mpw.Plugin.Initialize();
+ }
+ else
+ {
+ Debug.Fail("Missed managed plugin wrapper type");
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ debugLog.Error("Exception raised during initialzation of {asm}. It has been removed from the collection\n{ex}", plugin.ToString(), ex);
+ }
+
+ return false;
+ }
+
+ private static void LoadPlugin(IManagedPlugin plugin, ILogProvider debugLog)
+ {
+ Stopwatch sw = new();
+ try
+ {
+ sw.Start();
+
+ //Recover the base class used to load instances
+ if (plugin is ManagedPlugin mp)
+ {
+ mp.Plugin.LoadPlugins();
+ }
+ else if (plugin is ManualPluginWrapper mpw)
+ {
+ mpw.Load();
+ }
+ else
+ {
+ Debug.Fail("Missed managed plugin wrapper type");
+ }
+
+ sw.Stop();
+
+ debugLog.Verbose("Loaded {pl} in {tm} ms", plugin.ToString(), sw.ElapsedMilliseconds);
+ }
+ catch (Exception ex)
+ {
+ debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", plugin.ToString(), ex);
+ }
+ finally
+ {
+ sw.Stop();
+ }
+ }
+
+
+ private sealed record class ManagedPlugin(RuntimePluginLoader Plugin) : IManagedPlugin
+ {
+ private ServiceContainer? _services;
+
+ ///<inheritdoc/>
+ public IServiceContainer Services
+ {
+ get
+ {
+ _ = _services ?? throw new InvalidOperationException("The service container is not currently loaded");
+ return _services!;
+ }
+ }
+
+ ///<inheritdoc/>
+ IEndpoint[] IManagedPlugin.GetEndpoints() => Plugin.Controller.GetOnlyWebPlugins().SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray();
+
+ /*
+ * Automatically called after the plugin has successfully loaded
+ * by event handlers below
+ */
+
+ ///<inheritdoc/>
+ void IManagedPlugin.OnPluginLoaded()
+ {
+ //If the service container is defined, dispose
+ _services?.Dispose();
+
+ //Init new service container
+ _services = new();
+
+ //Get types from plugin
+ foreach (LivePlugin plugin in Plugin.Controller.Plugins)
+ {
+ /*
+ * Get the exposed configurator method if declared,
+ * it may not be defined.
+ */
+ ServiceConfigurator? callback = plugin.PluginType.GetMethods()
+ .Where(static m => m.GetCustomAttribute<ServiceConfiguratorAttribute>() != null && !m.IsAbstract)
+ .Select(m => m.CreateDelegate<ServiceConfigurator>(plugin.Plugin))
+ .FirstOrDefault();
+
+ //Invoke if defined to expose services
+ callback?.Invoke(_services);
+ }
+ }
+
+ ///<inheritdoc/>
+ void IManagedPlugin.OnPluginUnloaded()
+ {
+ //Cleanup services no longer in use. Plugin is still valid until this method returns
+ _services?.Dispose();
+
+ //Remove ref to services
+ _services = null;
+ }
+
+ ///<inheritdoc/>
+ bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp)
+ {
+ //Get plugin
+ LivePlugin? plugin = Plugin.Controller.Plugins.FirstOrDefault(p => p.PluginName.Equals(pluginName, comp));
+
+ //If plugin is null, return false
+ if (plugin == null)
+ {
+ return false;
+ }
+
+ return plugin.SendConsoleMessage(command);
+ }
+
+ public override string ToString() => Path.GetFileName(Plugin.Config.AssemblyFile);
+ }
+
+ private sealed record class ManualPluginWrapper(IManualPlugin Plugin) : IManagedPlugin, IDisposable
+ {
+ private ServiceContainer _container = new();
+
+ ///<inheritdoc/>
+ public IServiceContainer Services => _container;
+
+ ///<inheritdoc/>
+ IEndpoint[] IManagedPlugin.GetEndpoints() => Plugin.GetEndpoints();
+
+ public void Load()
+ {
+ Plugin.Load();
+ Plugin.GetAllExportedServices(Services);
+ }
+
+ public void Unload()
+ {
+ Plugin.Unload();
+
+ //Unload and re-init container
+ _container.Dispose();
+ _container = new();
+ }
+
+ public void Dispose()
+ {
+ //Dispose container
+ _container.Dispose();
+
+ //Dispose plugin
+ Plugin.Dispose();
+ }
+
+ ///<inheritdoc/>
+ bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp)
+ {
+
+ if (Plugin.Name.Equals(pluginName, comp))
+ {
+ Plugin.OnConsoleCommand(command);
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /*
+ * SHOULD NEVER BE CALLED
+ */
+ void IManagedPlugin.OnPluginLoaded() => throw new NotImplementedException();
+ void IManagedPlugin.OnPluginUnloaded() => throw new NotImplementedException();
+
+ ///<inheritdoc/>
+ public override string ToString() => Plugin.Name;
+ }
+
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs
index c8be44c..e504013 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs
@@ -23,7 +23,6 @@
*/
using System.Net;
-using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -80,28 +79,28 @@ namespace VNLib.Plugins.Essentials.ServiceStack
_endpointsForPlugins.Clear();
}
- internal void OnPluginLoaded(IManagedPlugin controller)
+ internal void OnPluginLoaded(IManagedPlugin plugin)
{
//Get all new endpoints for plugin
- IEndpoint[] newEndpoints = controller.Controller.GetOnlyWebPlugins().SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray();
+ IEndpoint[] newEndpoints = plugin.GetEndpoints();
//Add endpoints to dict
- _endpointsForPlugins.AddOrUpdate(controller, newEndpoints);
+ _endpointsForPlugins.AddOrUpdate(plugin, newEndpoints);
//Add endpoints to hosts
- _vHosts.TryForeach(v => v.OnRuntimeServiceAttach(controller, newEndpoints));
+ _vHosts.TryForeach(v => v.OnRuntimeServiceAttach(plugin, newEndpoints));
}
- internal void OnPluginUnloaded(IManagedPlugin controller)
+ internal void OnPluginUnloaded(IManagedPlugin plugin)
{
//Get the old endpoints from the controller referrence and remove them
- if (_endpointsForPlugins.TryGetValue(controller, out IEndpoint[]? oldEps))
+ if (_endpointsForPlugins.TryGetValue(plugin, out IEndpoint[]? oldEps))
{
//Remove the old endpoints
- _vHosts.TryForeach(v => v.OnRuntimeServiceDetach(controller, oldEps));
+ _vHosts.TryForeach(v => v.OnRuntimeServiceDetach(plugin, oldEps));
//remove controller ref
- _ = _endpointsForPlugins.Remove(controller);
+ _ = _endpointsForPlugins.Remove(plugin);
}
}
}