diff options
Diffstat (limited to 'lib/Plugins.Runtime/src')
-rw-r--r-- | lib/Plugins.Runtime/src/AsmFileWatcher.cs | 104 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/AssemblyWatcher.cs | 81 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/IPluginAssemblyLoadConfig.cs (renamed from lib/Plugins.Runtime/src/IPluginConfig.cs) | 6 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs | 2 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/IPluginConfigReader.cs | 43 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/LoaderExtensions.cs | 88 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/PluginStackBuilder.cs | 78 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs | 13 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/RuntimePluginLoader.cs | 2 |
9 files changed, 229 insertions, 188 deletions
diff --git a/lib/Plugins.Runtime/src/AsmFileWatcher.cs b/lib/Plugins.Runtime/src/AsmFileWatcher.cs deleted file mode 100644 index f2a0ca7..0000000 --- a/lib/Plugins.Runtime/src/AsmFileWatcher.cs +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Runtime -* File: AsmFileWatcher.cs -* -* AsmFileWatcher.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.IO; -using System.Threading; - -using VNLib.Utils; -using VNLib.Utils.Extensions; - -namespace VNLib.Plugins.Runtime -{ - internal sealed class AsmFileWatcher : VnDisposeable - { - public IPluginReloadEventHandler Handler { get; } - - private readonly IPluginAssemblyLoader _loaderSource; - private readonly Timer _delayTimer; - private readonly FileSystemWatcher _watcher; - - private bool _pause; - - public AsmFileWatcher(IPluginAssemblyLoader LoaderSource, IPluginReloadEventHandler handler) - { - Handler = handler; - _loaderSource = LoaderSource; - - string dir = Path.GetDirectoryName(LoaderSource.Config.AssemblyFile)!; - - //Configure watcher to notify only when the assembly file changes - _watcher = new FileSystemWatcher(dir) - { - Filter = "*.dll", - EnableRaisingEvents = false, - IncludeSubdirectories = true, - NotifyFilter = NotifyFilters.LastWrite, - }; - - //Configure listener - _watcher.Changed += OnFileChanged; - _watcher.Created += OnFileChanged; - - _watcher.EnableRaisingEvents = true; - - //setup delay timer to wait on the config - _delayTimer = new(OnTimeout, this, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); - } - - void OnFileChanged(object sender, FileSystemEventArgs e) - { - //if were already waiting to process an event, we dont need to stage another - if (_pause) - { - return; - } - - //Set pause flag - _pause = true; - - //Restart the timer to trigger reload event on elapsed - _delayTimer.Restart(_loaderSource.Config.ReloadDelay); - } - - private void OnTimeout(object? state) - { - _delayTimer.Stop(); - - //Fire event, let exception crash app - Handler.OnPluginUnloaded(_loaderSource); - - //Clear pause flag - _pause = false; - } - - protected override void Free() - { - _delayTimer.Dispose(); - - //Detach event handler and dispose watcher - _watcher.Changed -= OnFileChanged; - _watcher.Dispose(); - } - } -} diff --git a/lib/Plugins.Runtime/src/AssemblyWatcher.cs b/lib/Plugins.Runtime/src/AssemblyWatcher.cs index 1cf87b0..09b49dc 100644 --- a/lib/Plugins.Runtime/src/AssemblyWatcher.cs +++ b/lib/Plugins.Runtime/src/AssemblyWatcher.cs @@ -22,8 +22,13 @@ * along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/. */ +using System.IO; +using System.Threading; using System.Collections.Generic; +using VNLib.Utils; +using VNLib.Utils.Extensions; + namespace VNLib.Plugins.Runtime { internal sealed class AssemblyWatcher : IPluginAssemblyWatcher @@ -36,6 +41,7 @@ namespace VNLib.Plugins.Runtime _watchers = new(); } + ///<inheritdoc/> public void StopWatching(IPluginReloadEventHandler handler) { lock (_lock) @@ -49,6 +55,7 @@ namespace VNLib.Plugins.Runtime } } + ///<inheritdoc/> public void WatchAssembly(IPluginReloadEventHandler handler, IPluginAssemblyLoader loader) { lock(_lock) @@ -65,6 +72,78 @@ namespace VNLib.Plugins.Runtime //Store watcher _watchers.Add(handler, watcher); } - } + } + + private sealed class AsmFileWatcher : VnDisposeable + { + public IPluginReloadEventHandler Handler { get; } + + private readonly IPluginAssemblyLoader _loaderSource; + private readonly Timer _delayTimer; + private readonly FileSystemWatcher _watcher; + + private bool _pause; + + public AsmFileWatcher(IPluginAssemblyLoader LoaderSource, IPluginReloadEventHandler handler) + { + Handler = handler; + _loaderSource = LoaderSource; + + string dir = Path.GetDirectoryName(LoaderSource.Config.AssemblyFile)!; + + //Configure watcher to notify only when the assembly file changes + _watcher = new FileSystemWatcher(dir) + { + Filter = "*.dll", + EnableRaisingEvents = false, + IncludeSubdirectories = true, + NotifyFilter = NotifyFilters.LastWrite, + }; + + //Configure listener + _watcher.Changed += OnFileChanged; + _watcher.Created += OnFileChanged; + + _watcher.EnableRaisingEvents = true; + + //setup delay timer to wait on the config + _delayTimer = new(OnTimeout, this, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + } + + void OnFileChanged(object sender, FileSystemEventArgs e) + { + //if were already waiting to process an event, we dont need to stage another + if (_pause) + { + return; + } + + //Set pause flag + _pause = true; + + //Restart the timer to trigger reload event on elapsed + _delayTimer.Restart(_loaderSource.Config.ReloadDelay); + } + + private void OnTimeout(object? state) + { + _delayTimer.Stop(); + + //Fire event, let exception crash app + Handler.OnPluginUnloaded(_loaderSource); + + //Clear pause flag + _pause = false; + } + + protected override void Free() + { + _delayTimer.Dispose(); + + //Detach event handler and dispose watcher + _watcher.Changed -= OnFileChanged; + _watcher.Dispose(); + } + } } } diff --git a/lib/Plugins.Runtime/src/IPluginConfig.cs b/lib/Plugins.Runtime/src/IPluginAssemblyLoadConfig.cs index a460fc0..4b5996e 100644 --- a/lib/Plugins.Runtime/src/IPluginConfig.cs +++ b/lib/Plugins.Runtime/src/IPluginAssemblyLoadConfig.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Plugins.Runtime -* File: IPluginConfig.cs +* File: IPluginAssemblyLoadConfig.cs * -* IPluginConfig.cs is part of VNLib.Plugins.Runtime which is part +* IPluginAssemblyLoadConfig.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 @@ -31,7 +31,7 @@ namespace VNLib.Plugins.Runtime /// Represents configuration information for a <see cref="IPluginAssemblyLoader"/> /// instance. /// </summary> - public interface IPluginConfig + public interface IPluginAssemblyLoadConfig { /// <summary> /// A value that indicates if the instance is unlodable. diff --git a/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs b/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs index d75ac47..7767473 100644 --- a/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs +++ b/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs @@ -34,6 +34,6 @@ namespace VNLib.Plugins.Runtime /// <summary> /// Gets the plugin's configuration information /// </summary> - IPluginConfig Config { get; } + IPluginAssemblyLoadConfig Config { get; } } } diff --git a/lib/Plugins.Runtime/src/IPluginConfigReader.cs b/lib/Plugins.Runtime/src/IPluginConfigReader.cs new file mode 100644 index 0000000..d50e0ab --- /dev/null +++ b/lib/Plugins.Runtime/src/IPluginConfigReader.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: IPluginConfigReader.cs +* +* IPluginConfigReader.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.IO; + +namespace VNLib.Plugins.Runtime +{ + /// <summary> + /// Represents an object that gets configuration data for the desired assembly configuration + /// and writes that conifguration to the output stream. + /// </summary> + public interface IPluginConfigReader + { + /// <summary> + /// Gets the configuration data for the desired assembly configuration and writes that + /// configuration to the output stream. + /// </summary> + /// <param name="asmConfig">The assembly configuration to get the configuration data for</param> + /// <param name="outputStream">The stream to write the configuration file data to</param> + void ReadPluginConfigData(IPluginAssemblyLoadConfig asmConfig, Stream outputStream); + } +} diff --git a/lib/Plugins.Runtime/src/LoaderExtensions.cs b/lib/Plugins.Runtime/src/LoaderExtensions.cs index 4dc1253..c553f4b 100644 --- a/lib/Plugins.Runtime/src/LoaderExtensions.cs +++ b/lib/Plugins.Runtime/src/LoaderExtensions.cs @@ -25,6 +25,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; using System.Collections.Generic; @@ -259,24 +260,40 @@ namespace VNLib.Plugins.Runtime } /// <summary> - /// Specify the host configuration data to pass to the plugin + /// Configures the plugin stack to retrieve plugin-local json configuration files + /// from the same directory as the plugin assembly file. /// </summary> /// <param name="builder"></param> - /// <param name="hostConfig">A configuration element to pass to the plugin's host config element</param> + /// <param name="hostConfig">An optional 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) + public static PluginStackBuilder WithLocalJsonConfig(this PluginStackBuilder builder, in JsonElement? hostConfig) { _ = builder ?? throw new ArgumentNullException(nameof(builder)); - //Clone the host config into binary - using VnMemoryStream ms = new(); - using (Utf8JsonWriter writer = new(ms)) + LocalFilePluginConfigReader reader; + + //Host config is optional + if (hostConfig.HasValue) + { + //Clone the host config into binary + using VnMemoryStream ms = new(); + using (Utf8JsonWriter writer = new(ms)) + { + hostConfig.Value.WriteTo(writer); + } + + //Create a reader from the binary + reader = new LocalFilePluginConfigReader(ms.ToArray()); + } + else { - hostConfig.WriteTo(writer); + //Empty json + byte[] emptyJson = Encoding.UTF8.GetBytes("{}"); + reader = new LocalFilePluginConfigReader(emptyJson); } //Store binary - return builder.WithConfigurationData(ms.AsSpan()); + return builder.WithConfigurationReader(reader); } /// <summary> @@ -343,5 +360,60 @@ namespace VNLib.Plugins.Runtime }); } } + + /* + * Assumes plugin configuration data is stored in a json file with the same name as + * the plugin assembly but with a json extension. + * + * The json file is local for the specific plugin and is not shared between plugins. The host + * configuration is also required + */ + private sealed record class LocalFilePluginConfigReader(ReadOnlyMemory<byte> HostJson) : IPluginConfigReader + { + public void ReadPluginConfigData(IPluginAssemblyLoadConfig asmConfig, Stream configData) + { + //Allow comments and trailing commas + JsonDocumentOptions jdo = new() + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip, + }; + + //Config file is the same name as the assembly but with a json extension + string pluginConfigFile = Path.ChangeExtension(asmConfig.AssemblyFile, ".json"); + + using JsonDocument hConfig = JsonDocument.Parse(HostJson, 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(configData); + 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(configData); + merged.WriteTo(writer); + } + } + } } } diff --git a/lib/Plugins.Runtime/src/PluginStackBuilder.cs b/lib/Plugins.Runtime/src/PluginStackBuilder.cs index d05f489..eed08e2 100644 --- a/lib/Plugins.Runtime/src/PluginStackBuilder.cs +++ b/lib/Plugins.Runtime/src/PluginStackBuilder.cs @@ -25,17 +25,15 @@ 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> @@ -44,10 +42,10 @@ namespace VNLib.Plugins.Runtime private IPluginDiscoveryManager? DiscoveryManager; private bool HotReload; private TimeSpan ReloadDelay; - private byte[]? HostConfigData; + private IPluginConfigReader? PluginConfig; private ILogProvider? DebugLog; - private Func<IPluginConfig, IAssemblyLoader>? Loader; + private Func<IPluginAssemblyLoadConfig, IAssemblyLoader>? Loader; /// <summary> /// Shortcut constructor for easy fluent chaining. @@ -76,17 +74,17 @@ namespace VNLib.Plugins.Runtime HotReload = true; ReloadDelay = reloadDelay; return this; - } + } /// <summary> /// Specifies the JSON host configuration data to pass to the plugin /// </summary> - /// <param name="hostConfig"></param> + /// <param name="pluginConfig">The plugin configuration data</param> /// <returns>The current builder instance for chaining</returns> - public PluginStackBuilder WithConfigurationData(ReadOnlySpan<byte> hostConfig) + public PluginStackBuilder WithConfigurationReader(IPluginConfigReader pluginConfig) { //Store binary copy - HostConfigData = hostConfig.ToArray(); + PluginConfig = pluginConfig ?? throw new ArgumentNullException(nameof(pluginConfig)); return this; } @@ -96,7 +94,7 @@ namespace VNLib.Plugins.Runtime /// </summary> /// <param name="loaderFactory">The factory callback funtion</param> /// <returns>The current builder instance for chaining</returns> - public PluginStackBuilder WithLoaderFactory(Func<IPluginConfig, IAssemblyLoader> loaderFactory) + public PluginStackBuilder WithLoaderFactory(Func<IPluginAssemblyLoadConfig, IAssemblyLoader> loaderFactory) { Loader = loaderFactory; return this; @@ -121,9 +119,7 @@ namespace VNLib.Plugins.Runtime public IPluginStack ConfigureStack() { _ = DiscoveryManager ?? throw new ArgumentException("You must specify a plugin discovery manager"); - - //Create a default config if none was specified - HostConfigData ??= GetEmptyConfig(); + _ = PluginConfig ?? throw new ArgumentException("A plugin confuration reader must be specified"); //Clone the current builder state PluginStackBuilder clone = (PluginStackBuilder)MemberwiseClone(); @@ -131,8 +127,6 @@ namespace VNLib.Plugins.Runtime return new PluginStack(clone); } - private static byte[] GetEmptyConfig() => Encoding.UTF8.GetBytes("{}"); - /* * @@ -172,7 +166,7 @@ namespace VNLib.Plugins.Runtime //Create a loader for each plugin foreach (string pluginPath in pluginPaths) { - PlugingAssemblyConfig pConf = new(Builder.HostConfigData) + PlugingAssemblyConfig pConf = new(Builder.PluginConfig!) { AssemblyFile = pluginPath, WatchForReload = Builder.HotReload, @@ -200,7 +194,7 @@ namespace VNLib.Plugins.Runtime } } - internal sealed record class PluginAsmLoader(IAssemblyLoader Loader, IPluginConfig Config) : IPluginAssemblyLoader + internal sealed record class PluginAsmLoader(IAssemblyLoader Loader, IPluginAssemblyLoadConfig Config) : IPluginAssemblyLoader { ///<inheritdoc/> public void Dispose() => Loader.Dispose(); @@ -215,7 +209,7 @@ namespace VNLib.Plugins.Runtime public void Unload() => Loader.Unload(); } - internal sealed record class PlugingAssemblyConfig(ReadOnlyMemory<byte> HostConfig) : IPluginConfig + internal sealed record class PlugingAssemblyConfig(IPluginConfigReader Config) : IPluginAssemblyLoadConfig { ///<inheritdoc/> public bool Unloadable { get; init; } @@ -229,54 +223,8 @@ namespace VNLib.Plugins.Runtime ///<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); - } - } + public void ReadConfigurationData(Stream outputStream) => Config.ReadPluginConfigData(this, outputStream); } } } diff --git a/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs b/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs index 53f63b2..c961b4e 100644 --- a/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs +++ b/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime @@ -35,12 +35,15 @@ namespace VNLib.Plugins.Runtime public class PluginUnloadException : Exception { public PluginUnloadException() - {} + { } + public PluginUnloadException(string message) : base(message) - {} + { } + public PluginUnloadException(string message, Exception innerException) : base(message, innerException) - {} + { } + protected PluginUnloadException(SerializationInfo info, StreamingContext context) : base(info, context) - {} + { } } } diff --git a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs index 21d4691..a1231c9 100644 --- a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs +++ b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs @@ -46,7 +46,7 @@ namespace VNLib.Plugins.Runtime /// <summary> /// Gets the plugin assembly loader configuration information /// </summary> - public IPluginConfig Config => Loader.Config; + public IPluginAssemblyLoadConfig Config => Loader.Config; /// <summary> /// Gets the plugin lifecycle controller |