aboutsummaryrefslogtreecommitdiff
path: root/lib/Plugins.Runtime
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Plugins.Runtime')
-rw-r--r--lib/Plugins.Runtime/src/AsmFileWatcher.cs104
-rw-r--r--lib/Plugins.Runtime/src/AssemblyWatcher.cs81
-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.cs2
-rw-r--r--lib/Plugins.Runtime/src/IPluginConfigReader.cs43
-rw-r--r--lib/Plugins.Runtime/src/LoaderExtensions.cs88
-rw-r--r--lib/Plugins.Runtime/src/PluginStackBuilder.cs78
-rw-r--r--lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs13
-rw-r--r--lib/Plugins.Runtime/src/RuntimePluginLoader.cs2
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