aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-03-09 01:48:39 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-03-09 01:48:39 -0500
commitec99d0c948733ea379065e0ae37ab7702a1e4727 (patch)
tree31db8b37a7850e56d64365d13cc276596c91f073 /lib
parent282aad617b9c39a6f14c1cf527f6dd4523d0c54b (diff)
Omega cache, session, and account provider complete overhaul
Diffstat (limited to 'lib')
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs9
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs9
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs5
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj17
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs5
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj19
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs103
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs42
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs83
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IAsyncConfigurable.cs42
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IConfigScope.cs47
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs38
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs237
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Events/AsyncIntervalAttribute.cs44
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs4
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs374
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs17
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs13
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/UserLoading.cs93
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs138
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj23
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs4
-rw-r--r--lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj16
23 files changed, 1085 insertions, 297 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
index dcde80a..fabd54e 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -22,6 +22,8 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
+
using Microsoft.EntityFrameworkCore;
namespace VNLib.Plugins.Extensions.Data.Storage
@@ -40,9 +42,8 @@ namespace VNLib.Plugins.Extensions.Data.Storage
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- //Set table name
- modelBuilder.Entity<LWStorageEntry>()
- .ToTable(TableName);
+ if(TableName != null)
+ throw new NotImplementedException("Table/relational package requires development not yet implemented");
}
}
} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs
index 939d3e3..cc9bd46 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -33,7 +33,6 @@ using System.Text.Json.Serialization;
using VNLib.Utils;
using VNLib.Utils.Async;
-using VNLib.Utils.Extensions;
using VNLib.Utils.Memory;
namespace VNLib.Plugins.Extensions.Data.Storage
@@ -108,7 +107,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
//Decode and deserialize the data
return BrotliDecoder.TryDecompress(Entry.Data, decodeBuffer, out int written)
- ? JsonSerializer.Deserialize<Dictionary<string, string>>(Entry.Data, SerializerOptions) ?? new(StringComparer.OrdinalIgnoreCase)
+ ? JsonSerializer.Deserialize<Dictionary<string, string>>(decodeBuffer.Span[..written], SerializerOptions) ?? new(StringComparer.OrdinalIgnoreCase)
: throw new InvalidDataException("Failed to decompress data");
}
}
@@ -122,7 +121,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
{
Check();
//De-serialize and return object
- return StringStorage.Value.TryGetValue(key, out string? val) ? val.AsJsonObject<T>(SerializerOptions) : default;
+ return StringStorage.Value.TryGetValue(key, out string? val) ? JsonSerializer.Deserialize<T>(val, SerializerOptions) : default;
}
/// <inheritdoc/>
@@ -138,7 +137,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
else
{
//Serialize the object to a string
- string value = obj.ToJsonString(SerializerOptions)!;
+ string value = JsonSerializer.Serialize(obj, SerializerOptions);
//Attempt to store string in storage
SetStringValue(key, value);
}
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
index 8a34ec4..eabe618 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -272,8 +272,9 @@ namespace VNLib.Plugins.Extensions.Data.Storage
{
throw new InvalidDataException("Failed to compress the descriptor data");
}
+
//Set the data
- entry.Data = encBuffer.Span.ToArray();
+ entry.Data = encBuffer.Span[..compressed].ToArray();
}
//Update modified time
entry.LastModified = DateTime.UtcNow;
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj
index 67310b6..0616048 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj
+++ b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj
@@ -4,20 +4,24 @@
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>VNLib.Plugins.Extensions.Data</RootNamespace>
<AssemblyName>VNLib.Plugins.Extensions.Data</AssemblyName>
- <Version>1.0.1.1</Version>
<Nullable>enable</Nullable>
- <SignAssembly>True</SignAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
- <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
<AnalysisLevel>latest-all</AnalysisLevel>
</PropertyGroup>
<PropertyGroup>
<Authors>Vaughn Nugent</Authors>
- <Description>Data extensions for VNLib Plugins</Description>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Extensions.Data</Product>
+ <PackageId>VNLib.Plugins.Extensions.Data</PackageId>
+ <Description>
+ An Essentials framework extension library that provides data structures for abstractions between data and
+ databases, using the EntityFrameworkCore library and design patterns.
+ </Description>
<Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
- <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Data</RepositoryUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -37,8 +41,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.13" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.13" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.14" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
index 9d3d2e0..c2d7726 100644
--- a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading.Sql
@@ -23,7 +23,6 @@
*/
using System;
-using System.Text.Json;
using System.Data.Common;
using System.Collections.Generic;
@@ -63,7 +62,7 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
private static Func<DbConnection> FactoryLoader(PluginBase plugin)
{
- IReadOnlyDictionary<string, JsonElement> sqlConf = plugin.GetConfig(SQL_CONFIG_KEY);
+ IConfigScope sqlConf = plugin.GetConfig(SQL_CONFIG_KEY);
//Get the db-type
string? type = sqlConf.GetPropString("db_type");
diff --git a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj
index f944cd6..3d61b67 100644
--- a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj
+++ b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj
@@ -4,18 +4,23 @@
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>VNLib.Plugins.Extensions.Loading.Sql</RootNamespace>
<AssemblyName>VNLib.Plugins.Extensions.Loading.Sql</AssemblyName>
- <Version>1.0.1.1</Version>
<Nullable>enable</Nullable>
- <SignAssembly>True</SignAssembly>
- <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
<AnalysisLevel>latest-all</AnalysisLevel>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
-
+
<PropertyGroup>
<Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Extensions.Loading.Sql</Product>
+ <PackageId>VVNLib.Plugins.Extensions.Loading.Sql</PackageId>
+ <Description>
+ An Essentials framework extension library for configuring database connections using the EntityFrameworkCore
+ patterns and abstractions. Supports MSSql, MySql and SqlLite database connections via plugin configuration.
+ </Description>
<Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
- <PackageProjectUrl>https://www.vaughnnugent.com/resources/software</PackageProjectUrl>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Loading.Sql</RepositoryUrl>
</PropertyGroup>
@@ -28,8 +33,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.13" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.13" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.14" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.14" />
<PackageReference Include="MySqlConnector" Version="2.2.5" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
</ItemGroup>
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs b/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
index 2827587..dcc5f59 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -23,14 +23,14 @@
*/
using System;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Reflection;
using System.Runtime.Loader;
-using System.Collections.Generic;
-
-using McMaster.NETCore.Plugins;
+using System.Runtime.InteropServices;
+using VNLib.Utils.IO;
using VNLib.Utils.Resources;
namespace VNLib.Plugins.Extensions.Loading
@@ -48,24 +48,55 @@ namespace VNLib.Plugins.Extensions.Loading
/// <typeparam name="T">The exported type to manage</typeparam>
public sealed class AssemblyLoader<T> : OpenResourceHandle<T>
{
- private readonly PluginLoader _loader;
private readonly CancellationTokenRegistration _reg;
private readonly Lazy<T> _instance;
+ private readonly AssemblyLoadContext _loadContext;
+ private readonly AssemblyDependencyResolver _resolver;
+ private readonly string _assemblyPath;
/// <summary>
/// The instance of the loaded type
/// </summary>
public override T Resource => _instance.Value;
- private AssemblyLoader(PluginLoader loader, in CancellationToken unloadToken)
+ private AssemblyLoader(string assemblyPath, AssemblyLoadContext parentContext, CancellationToken unloadToken)
{
- _loader = loader;
+ _loadContext = parentContext;
+ _resolver = new(assemblyPath);
+ _assemblyPath = assemblyPath;
+
+ //Add resolver for context
+ parentContext.Resolving += OnDependencyResolving;
+ parentContext.ResolvingUnmanagedDll += OnNativeLibraryResolving;
+
//Init lazy loader
_instance = new(LoadAndGetExportedType, LazyThreadSafetyMode.PublicationOnly);
//Register dispose
_reg = unloadToken.Register(Dispose);
}
-
+
+ /*
+ * Resolves a native libary isolated to the requested assembly, which
+ * should be isolated to this assembly or one of its dependencies.
+ */
+ private IntPtr OnNativeLibraryResolving(Assembly assembly, string libname)
+ {
+ //Resolve the desired asm dependency for the current context
+ string? requestedDll = _resolver.ResolveUnmanagedDllToPath(libname);
+
+ //if the dep is resolved, seach in the assembly directory for the manageed dll only
+ return requestedDll == null ? IntPtr.Zero : NativeLibrary.Load(requestedDll, assembly, DllImportSearchPath.AssemblyDirectory);
+ }
+
+ private Assembly? OnDependencyResolving(AssemblyLoadContext context, AssemblyName asmName)
+ {
+ //Resolve the desired asm dependency for the current context
+ string? desiredAsm = _resolver.ResolveAssemblyToPath(asmName);
+
+ //If the asm exists in the dir, load it
+ return desiredAsm == null ? null : _loadContext.LoadFromAssemblyPath(desiredAsm);
+ }
+
/// <summary>
/// Loads the default assembly and gets the expected export type,
/// creates a new instance, and calls its parameterless constructor
@@ -74,8 +105,8 @@ namespace VNLib.Plugins.Extensions.Loading
/// <exception cref="EntryPointNotFoundException"></exception>
private T LoadAndGetExportedType()
{
- //Load the assembly
- Assembly asm = _loader.LoadDefaultAssembly();
+ //Load the assembly into the parent context
+ Assembly asm = _loadContext.LoadFromAssemblyPath(_assemblyPath);
Type resourceType = typeof(T);
@@ -109,54 +140,44 @@ namespace VNLib.Plugins.Extensions.Loading
///<inheritdoc/>
protected override void Free()
{
+ //Remove resolving event handlers
+ _loadContext.Resolving -= OnDependencyResolving;
+ _loadContext.ResolvingUnmanagedDll -= OnNativeLibraryResolving;
+
//If the instance is disposable, call its dispose method on unload
if (_instance.IsValueCreated && _instance.Value is IDisposable)
{
(_instance.Value as IDisposable)?.Dispose();
}
- _loader.Dispose();
_reg.Dispose();
}
/// <summary>
- /// Creates a new assembly loader for the specified type and
+ /// Creates a new loader for the desired assembly. The assembly and its dependencies
+ /// will be loaded into the parent context, meaning all loaded types belong to the
+ /// current <see cref="AssemblyLoadContext"/> which belongs the current plugin instance.
/// </summary>
/// <param name="assemblyName">The name of the assmbly within the current plugin directory</param>
/// <param name="unloadToken">The plugin unload token</param>
+ /// <exception cref="FileNotFoundException"></exception>
internal static AssemblyLoader<T> Load(string assemblyName, CancellationToken unloadToken)
{
- Assembly executingAsm = Assembly.GetExecutingAssembly();
- AssemblyLoadContext currentCtx = AssemblyLoadContext.GetLoadContext(executingAsm) ?? throw new InvalidOperationException("Could not get default assembly load context");
-
- List<Type> shared = new ()
+ //Make sure the file exists
+ if (!FileOperations.FileExists(assemblyName))
{
- typeof(T),
- typeof(PluginBase),
- };
+ throw new FileNotFoundException($"The desired assembly {assemblyName} could not be found at the file path");
+ }
- //Share all VNLib internal libraries
- shared.AddRange(currentCtx.Assemblies.Where(static s => s.FullName.Contains("VNLib", StringComparison.OrdinalIgnoreCase)).SelectMany(static s => s.GetExportedTypes()));
+ /*
+ * Dynamic assemblies are loaded directly to the exe assembly context.
+ * This should always be the plugin isolated context.
+ */
- PluginLoader loader = PluginLoader.CreateFromAssemblyFile(assemblyName,
- currentCtx.IsCollectible,
- shared.ToArray(),
- conf =>
- {
-
- /*
- * Load context is required to be set to the executing assembly's load context
- * because it is controlled by the host, so this loader should be considered a
- * a "child" collection of assemblies
- */
- conf.DefaultContext = currentCtx;
-
- conf.PreferSharedTypes = true;
-
- //Share utils asm
- conf.SharedAssemblies.Add(typeof(Utils.Memory.MemoryUtil).Assembly.GetName());
- });
-
- return new(loader, in unloadToken);
+ Assembly executingAsm = Assembly.GetExecutingAssembly();
+ AssemblyLoadContext currentCtx = AssemblyLoadContext.GetLoadContext(executingAsm) ?? throw new InvalidOperationException("Could not get default assembly load context");
+
+ return new(assemblyName, currentCtx, unloadToken);
}
+
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs
new file mode 100644
index 0000000..83ce558
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigrationValidationException.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: ConfigrationValidationException.cs
+*
+* ConfigrationValidationException.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading 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 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading 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;
+
+namespace VNLib.Plugins.Extensions.Loading
+{
+ /// <summary>
+ /// An exception raised when a configuration validation exception has occured
+ /// </summary>
+ public class ConfigrationValidationException : Exception
+ {
+ public ConfigrationValidationException(string message) : base(message)
+ {}
+
+ public ConfigrationValidationException(string message, Exception innerException) : base(message, innerException)
+ {}
+ public ConfigrationValidationException()
+ {}
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs
new file mode 100644
index 0000000..7f5c09c
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs
@@ -0,0 +1,83 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: ConfigScope.cs
+*
+* ConfigScope.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading 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 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading 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.Text.Json;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+
+namespace VNLib.Plugins.Extensions.Loading
+{
+ internal sealed class ConfigScope: IConfigScope
+ {
+
+ private readonly Lazy<IReadOnlyDictionary<string, JsonElement>> _config;
+
+ private readonly JsonElement _element;
+
+ internal ConfigScope(JsonElement element, string scopeName)
+ {
+ _element = element;
+ ScopeName = scopeName;
+ _config = new(LoadTable);
+ }
+
+ private IReadOnlyDictionary<string, JsonElement> LoadTable()
+ {
+ return _element.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
+ }
+
+ ///<inheritdoc/>
+ public JsonElement this[string key] => _config.Value[key];
+
+ ///<inheritdoc/>
+ public IEnumerable<string> Keys => _config.Value.Keys;
+
+ ///<inheritdoc/>
+ public IEnumerable<JsonElement> Values => _config.Value.Values;
+
+ ///<inheritdoc/>
+ public int Count => _config.Value.Count;
+
+ ///<inheritdoc/>
+ public string ScopeName { get; }
+
+ ///<inheritdoc/>
+ public bool ContainsKey(string key) => _config.Value.ContainsKey(key);
+
+ ///<inheritdoc/>
+ public T Deserialze<T>() => _element.Deserialize<T>()!;
+
+ ///<inheritdoc/>
+ public IEnumerator<KeyValuePair<string, JsonElement>> GetEnumerator() => _config.Value.GetEnumerator();
+
+ ///<inheritdoc/>
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out JsonElement value) => _config.Value.TryGetValue(key, out value);
+
+ IEnumerator IEnumerable.GetEnumerator() => _config.Value.GetEnumerator();
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IAsyncConfigurable.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IAsyncConfigurable.cs
new file mode 100644
index 0000000..2c51da2
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IAsyncConfigurable.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: IAsyncConfigurable.cs
+*
+* IAsyncConfigurable.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading 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 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading 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.Threading.Tasks;
+
+namespace VNLib.Plugins.Extensions.Loading
+{
+ /// <summary>
+ /// Allows for asynchronous service configuration during service creation, that
+ /// will be observed on the plugin
+ /// </summary>
+ public interface IAsyncConfigurable
+ {
+ /// <summary>
+ /// Configures the service for use. Exceptions will be written to the
+ /// plugin's default log provider
+ /// </summary>
+ /// <returns>A task that completes when the service has been loaded successfully</returns>
+ Task ConfigureServiceAsync(PluginBase plugin);
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IConfigScope.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IConfigScope.cs
new file mode 100644
index 0000000..af6f181
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IConfigScope.cs
@@ -0,0 +1,47 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: IConfigScope.cs
+*
+* IConfigScope.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading 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 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading 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.Text.Json;
+using System.Collections.Generic;
+
+namespace VNLib.Plugins.Extensions.Loading
+{
+ /// <summary>
+ /// A top-level scoped configuration element
+ /// </summary>
+ public interface IConfigScope : IReadOnlyDictionary<string, JsonElement>
+ {
+ /// <summary>
+ /// The root level name of the configuration element
+ /// </summary>
+ string ScopeName { get; }
+
+ /// <summary>
+ /// Json deserialzes the current config scope to the desired type
+ /// </summary>
+ /// <typeparam name="T">The type to deserialze the current config to</typeparam>
+ /// <returns>The instance created from the current scope</returns>
+ T Deserialze<T>();
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs
new file mode 100644
index 0000000..6d4641b
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: IOnConfigValidation.cs
+*
+* IOnConfigValidation.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading 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 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading 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/.
+*/
+
+namespace VNLib.Plugins.Extensions.Loading
+{
+ /// <summary>
+ /// Called when a configuration deserialzation occurs, to validate
+ /// the configuration.
+ /// </summary>
+ public interface IOnConfigValidation
+ {
+ /// <summary>
+ /// Validates a json configuration during deserialzation
+ /// </summary>
+ void Validate();
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
index 2bba84a..0ae6ed6 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -23,12 +23,10 @@
*/
using System;
-using System.Linq;
using System.Text.Json;
using System.Reflection;
using System.Collections.Generic;
-
-using VNLib.Utils.Extensions;
+using System.Diagnostics.CodeAnalysis;
namespace VNLib.Plugins.Extensions.Loading
{
@@ -52,8 +50,15 @@ namespace VNLib.Plugins.Extensions.Loading
{
ConfigVarName = configVarName;
}
+
+ /// <summary>
+ /// When true or not configured, signals that the type requires a configuration scope
+ /// when loaded. When false, and configuration is not found, signals to the service loading
+ /// system to continue without configuration
+ /// </summary>
+ public bool Required { get; init; } = true;
}
-
+
/// <summary>
/// Contains extensions for plugin configuration specifc extensions
/// </summary>
@@ -69,22 +74,26 @@ namespace VNLib.Plugins.Extensions.Loading
/// <typeparam name="T">The type to get the configuration of</typeparam>
/// <param name="plugin"></param>
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfigForType<T>(this PluginBase plugin)
+ public static IConfigScope GetConfigForType<T>(this PluginBase plugin)
{
Type t = typeof(T);
return plugin.GetConfigForType(t);
}
+
/// <summary>
- /// Retrieves a top level configuration dictionary of elements with the specified property name,
- /// from the plugin config first, or falls back to the host config file
+ /// Retrieves a top level configuration dictionary of elements with the specified property name.
/// </summary>
+ /// <remarks>
+ /// Search order: Plugin config, fall back to host config, throw if not found
+ /// </remarks>
/// <param name="plugin"></param>
/// <param name="propName">The config property name to retrieve</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
/// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfig(this PluginBase plugin, string propName)
+ public static IConfigScope GetConfig(this PluginBase plugin, string propName)
{
plugin.ThrowIfUnloaded();
try
@@ -96,29 +105,33 @@ namespace VNLib.Plugins.Extensions.Loading
el = plugin.HostConfig.GetProperty(propName);
}
//Get the top level config as a dictionary
- return el.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
+ return new ConfigScope(el, propName);
}
- catch(KeyNotFoundException)
+ catch (KeyNotFoundException)
{
throw new KeyNotFoundException($"Missing required top level configuration object '{propName}', in host/plugin configuration files");
}
}
+
/// <summary>
/// Retrieves a top level configuration dictionary of elements with the specified property name,
- /// from the plugin config first, or falls back to the host config file
+ /// or null if no configuration could be found
/// </summary>
+ /// <remarks>
+ /// Search order: Plugin config, fall back to host config, null not found
+ /// </remarks>
/// <param name="plugin"></param>
/// <param name="propName">The config property name to retrieve</param>
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement>? TryGetConfig(this PluginBase plugin, string propName)
+ public static IConfigScope? TryGetConfig(this PluginBase plugin, string propName)
{
plugin.ThrowIfUnloaded();
//Try to get the element from the plugin config first, or fallback to host
if (plugin.PluginConfig.TryGetProperty(propName, out JsonElement el) || plugin.HostConfig.TryGetProperty(propName, out el))
{
//Get the top level config as a dictionary
- return el.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
+ return new ConfigScope(el, propName);
}
//No config found
return null;
@@ -130,15 +143,65 @@ namespace VNLib.Plugins.Extensions.Loading
/// </summary>
/// <param name="plugin"></param>
/// <param name="type">The type to get configuration data for</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfigForType(this PluginBase plugin, Type type)
+ public static IConfigScope GetConfigForType(this PluginBase plugin, Type type)
+ {
+ _ = type ?? throw new ArgumentNullException(nameof(type));
+
+ string? configName = GetConfigNameForType(type);
+
+ if (configName == null)
+ {
+ ThrowConfigNotFoundForType(type);
+ }
+
+ return plugin.GetConfig(configName);
+ }
+
+ /// <summary>
+ /// Gets the configuration property name for the type
+ /// </summary>
+ /// <param name="type">The type to get the configuration name for</param>
+ /// <returns>The configuration property element name</returns>
+ public static string? GetConfigNameForType(Type type)
{
//Get config name attribute from plugin type
- ConfigurationNameAttribute? configName = type.GetCustomAttribute<ConfigurationNameAttribute>();
- return configName?.ConfigVarName == null
- ? throw new KeyNotFoundException("No configuration attribute set")
- : plugin.GetConfig(configName.ConfigVarName);
+ return type.GetCustomAttribute<ConfigurationNameAttribute>()?.ConfigVarName;
+ }
+
+ /// <summary>
+ /// Determines if the type requires a configuration element.
+ /// </summary>
+ /// <param name="type">The type to determine config required status</param>
+ /// <returns>
+ /// True if the configuration is required, or false if the <see cref="ConfigurationNameAttribute"/>
+ /// was not declared, or <see cref="ConfigurationNameAttribute.Required"/> is false
+ /// </returns>
+ public static bool ConfigurationRequired(Type type)
+ {
+ return type.GetCustomAttribute<ConfigurationNameAttribute>()?.Required ?? false;
+ }
+
+ /// <summary>
+ /// Throws a <see cref="KeyNotFoundException"/> with proper diagnostic information
+ /// for missing configuration for a given type
+ /// </summary>
+ /// <param name="type">The type to raise exception for</param>
+ /// <exception cref="KeyNotFoundException"></exception>
+ [DoesNotReturn]
+ public static void ThrowConfigNotFoundForType(Type type)
+ {
+ //Try to get the config property name for the type
+ string? configName = GetConfigNameForType(type);
+ if (configName != null)
+ {
+ throw new KeyNotFoundException($"Missing required configuration key {configName} for type {type.Name}");
+ }
+ else
+ {
+ throw new KeyNotFoundException($"Missing required configuration key for type {type.Name}");
+ }
}
/// <summary>
@@ -147,15 +210,38 @@ namespace VNLib.Plugins.Extensions.Loading
/// </summary>
/// <param name="obj">The object that a configuration can be retrieved for</param>
/// <param name="plugin">The plugin containing configuration variables</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfig(this PluginBase plugin, object obj)
+ public static IConfigScope GetConfig(this PluginBase plugin, object obj)
{
Type t = obj.GetType();
return plugin.GetConfigForType(t);
}
/// <summary>
+ /// Deserialzes the configuration to the desired object and calls its
+ /// <see cref="IOnConfigValidation.Validate"/> method. Validation exceptions
+ /// are wrapped in a <see cref="ConfigrationValidationException"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="scope"></param>
+ /// <returns></returns>
+ /// <exception cref="ConfigrationValidationException"></exception>
+ public static T DeserialzeAndValidate<T>(this IConfigScope scope) where T : IOnConfigValidation
+ {
+ T conf = scope.Deserialze<T>();
+ try
+ {
+ conf.Validate();
+ }
+ catch(Exception ex)
+ {
+ throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(T).Name}", ex);
+ }
+ return conf;
+ }
+
+ /// <summary>
/// Determines if the current plugin configuration contains the require properties to initialize
/// the type
/// </summary>
@@ -171,6 +257,94 @@ namespace VNLib.Plugins.Extensions.Loading
}
/// <summary>
+ /// Gets a given configuration element from the global configuration scope
+ /// and deserializes it into the desired type.
+ /// <para>
+ /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.Validate"/>
+ /// method is invoked, and exceptions are warpped in <see cref="ConfigrationValidationException"/>
+ /// </para>
+ /// <para>
+ /// If the type inherits <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync(PluginBase)"/>
+ /// method is called by the service scheduler
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TConfig">The configuration type</typeparam>
+ /// <param name="plugin"></param>
+ /// <returns>The deserialzed configuration element</returns>
+ /// <exception cref="ConfigrationValidationException"></exception>
+ public static TConfig GetConfigElement<TConfig>(this PluginBase plugin)
+ {
+ //Deserialze the element
+ TConfig config = plugin.GetConfigForType<TConfig>().Deserialze<TConfig>();
+
+ //If the type is validatable, validate it
+ if(config is IOnConfigValidation conf)
+ {
+ try
+ {
+ conf.Validate();
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex);
+ }
+ }
+
+ //If async config, load async
+ if(config is IAsyncConfigurable ac)
+ {
+ _ = plugin.ConfigureServiceAsync(ac);
+ }
+
+ return config;
+ }
+
+ /// <summary>
+ /// Gets a given configuration element from the global configuration scope
+ /// and deserializes it into the desired type.
+ /// <para>
+ /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.Validate"/>
+ /// method is invoked, and exceptions are warpped in <see cref="ConfigrationValidationException"/>
+ /// </para>
+ /// <para>
+ /// If the type inherits <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync(PluginBase)"/>
+ /// method is called by the service scheduler
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TConfig">The configuration type</typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="elementName">The configuration element name override</param>
+ /// <returns>The deserialzed configuration element</returns>
+ /// <exception cref="ConfigrationValidationException"></exception>
+ public static TConfig GetConfigElement<TConfig>(this PluginBase plugin, string elementName)
+ {
+ //Deserialze the element
+ TConfig config = plugin.GetConfig(elementName).Deserialze<TConfig>();
+
+ //If the type is validatable, validate it
+ if (config is IOnConfigValidation conf)
+ {
+ try
+ {
+ conf.Validate();
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex);
+ }
+ }
+
+ //If async config, load async
+ if (config is IAsyncConfigurable ac)
+ {
+ _ = plugin.ConfigureServiceAsync(ac);
+ }
+
+ return config;
+ }
+
+
+ /// <summary>
/// Attempts to load the basic S3 configuration variables required
/// for S3 client access
/// </summary>
@@ -179,21 +353,8 @@ namespace VNLib.Plugins.Extensions.Loading
public static S3Config? TryGetS3Config(this PluginBase plugin)
{
//Try get the config
- IReadOnlyDictionary<string, JsonElement>? s3conf = plugin.TryGetConfig(S3_CONFIG);
- if(s3conf == null)
- {
- return null;
- }
-
- //Try get the elements
- return new()
- {
- BaseBucket = s3conf.GetPropString("bucket"),
- ClientId = s3conf.GetPropString("access_key"),
- ServerAddress = s3conf.GetPropString("server_address"),
- UseSsl = s3conf.TryGetValue("use_ssl", out JsonElement el) && el.GetBoolean(),
- Region = s3conf.GetPropString("region"),
- };
+ IConfigScope? s3conf = plugin.TryGetConfig(S3_CONFIG);
+ return s3conf?.Deserialze<S3Config>();
}
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Events/AsyncIntervalAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Events/AsyncIntervalAttribute.cs
index e8f071e..139a3ac 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Events/AsyncIntervalAttribute.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Events/AsyncIntervalAttribute.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -36,12 +36,46 @@ namespace VNLib.Plugins.Extensions.Loading.Events
internal readonly TimeSpan Interval;
/// <summary>
- /// Intializes the <see cref="AsyncIntervalAttribute"/> with the specified timeout in milliseconds
+ /// Initializes a new <see cref="AsyncIntervalAttribute"/> with allowing
+ /// a configurable
/// </summary>
- /// <param name="milliseconds">The interval in milliseconds</param>
- public AsyncIntervalAttribute(int milliseconds)
+ public AsyncIntervalAttribute()
+ {}
+
+ /// <summary>
+ /// Gets or sets the interval in seconds. Choose only ONE internval resolution
+ /// </summary>
+ public int Seconds
+ {
+ get => (int)Interval.TotalSeconds;
+ init => Interval = TimeSpan.FromSeconds(value);
+ }
+
+ /// <summary>
+ /// Gets or sets the interval in milliseconds. Choose only ONE internval resolution
+ /// </summary>
+ public int MilliSeconds
+ {
+ get => (int)Interval.TotalMilliseconds;
+ init => Interval = TimeSpan.FromMilliseconds(value);
+ }
+
+ /// <summary>
+ /// Gets or sets the interval in minutes. Choose only ONE internval resolution
+ /// </summary>
+ public int Minutes
+ {
+ get => (int)Interval.TotalMinutes;
+ init => Interval = TimeSpan.FromMinutes(value);
+ }
+
+ /// <summary>
+ /// Gets or sets the interval in hours. Choose only ONE internval resolution
+ /// </summary>
+ public int Hours
{
- Interval = TimeSpan.FromMilliseconds(milliseconds);
+ get => (int)Interval.TotalMinutes;
+ init => Interval = TimeSpan.FromHours(value);
}
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs
index bde6986..57e4a9c 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -60,7 +60,7 @@ namespace VNLib.Plugins.Extensions.Loading.Events
plugin.Log.Verbose("Interval for {t} scheduled", interval);
//Run interval on plugins bg scheduler
- _ = plugin.ObserveTask(() => RunIntervalOnPluginScheduler(plugin, asyncCallback, interval, immediate));
+ _ = plugin.ObserveWork(() => RunIntervalOnPluginScheduler(plugin, asyncCallback, interval, immediate));
}
private static async Task RunIntervalOnPluginScheduler(PluginBase plugin, AsyncSchedulableCallback callback, TimeSpan interval, bool immediate)
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
index 743566d..62af9e3 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -26,6 +26,7 @@ using System;
using System.IO;
using System.Linq;
using System.Text.Json;
+using System.Reflection;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -37,7 +38,7 @@ using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials.Accounts;
namespace VNLib.Plugins.Extensions.Loading
-{
+{
/// <summary>
/// Provides common loading (and unloading when required) extensions for plugins
@@ -103,45 +104,12 @@ namespace VNLib.Plugins.Extensions.Loading
/// <exception cref="OverflowException"></exception>
/// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static PasswordHashing GetPasswords(this PluginBase plugin)
+ public static IPasswordHashingProvider GetPasswords(this PluginBase plugin)
{
plugin.ThrowIfUnloaded();
- //Get/load the passwords one time only
- return GetOrCreateSingleton(plugin, LoadPasswords);
+ //Check if a password configuration element is loaded, otherwise load with defaults
+ return plugin.GetOrCreateSingleton<SecretProvider>().Passwords;
}
-
- private static PasswordHashing LoadPasswords(PluginBase plugin)
- {
- PasswordHashing Passwords;
-
- //Create new session provider
- SecretProvider secrets = new();
-
- //Load the secret in the background
- secrets.LoadSecret(plugin);
-
- //See hashing params are defined
- IReadOnlyDictionary<string, JsonElement>? hashingArgs = plugin.TryGetConfig(PASSWORD_HASHING_KEY);
- if (hashingArgs != null)
- {
- //Get hashing arguments
- uint saltLen = hashingArgs["salt_len"].GetUInt32();
- uint hashLen = hashingArgs["hash_len"].GetUInt32();
- uint timeCost = hashingArgs["time_cost"].GetUInt32();
- uint memoryCost = hashingArgs["memory_cost"].GetUInt32();
- uint parallelism = hashingArgs["parallelism"].GetUInt32();
- //Load passwords
- Passwords = new(secrets, (int)saltLen, timeCost, memoryCost, parallelism, hashLen);
- }
- else
- {
- //Init default password hashing
- Passwords = new(secrets);
- }
- //return
- return Passwords;
- }
-
/// <summary>
/// Loads an assembly into the current plugins AppDomain and will unload when disposed
@@ -165,21 +133,21 @@ namespace VNLib.Plugins.Extensions.Loading
_ = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName));
//get plugin directory from config
- IReadOnlyDictionary<string, JsonElement> config = plugin.GetConfig("plugins");
+ IConfigScope config = plugin.GetConfig("plugins");
/*
* Allow an assets directory to limit the scope of the search for the desired
* assembly, otherwise search all plugins directories
*/
- string? assetDir = config.GetPropString("assets");
+ string? assetDir = config.GetPropString(PLUGIN_ASSET_KEY);
assetDir ??= config["path"].GetString();
/*
* This should never happen since this method can only be called from a
* plugin context, which means this path was used to load the current plugin
*/
- _ = assetDir ?? throw new ArgumentNullException("assets", "No plugin path is defined for the current host configuration, this is likely a bug");
+ _ = assetDir ?? throw new ArgumentNullException(PLUGIN_ASSET_KEY, "No plugin path is defined for the current host configuration, this is likely a bug");
//Get the first file that matches the search file
string? asmFile = Directory.EnumerateFiles(assetDir, assemblyName, dirSearchOption).FirstOrDefault();
@@ -187,8 +155,7 @@ namespace VNLib.Plugins.Extensions.Loading
//Load the assembly
return AssemblyLoader<T>.Load(asmFile, plugin.UnloadToken);
- }
-
+ }
/// <summary>
/// Determintes if the current plugin config has a debug propety set
@@ -226,7 +193,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <param name="delayMs">An optional startup delay for the operation</param>
/// <returns>A task that completes when the deferred task completes </returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static async Task ObserveTask(this PluginBase plugin, Func<Task> asyncTask, int delayMs = 0)
+ public static async Task ObserveWork(this PluginBase plugin, Func<Task> asyncTask, int delayMs = 0)
{
/*
* Motivation:
@@ -267,7 +234,6 @@ namespace VNLib.Plugins.Extensions.Loading
}
}
-
/// <summary>
/// Schedules work to begin after the specified delay to be observed by the plugin while
/// passing plugin specifie information. Exceptions are logged to the default plugin log
@@ -278,22 +244,22 @@ namespace VNLib.Plugins.Extensions.Loading
/// <returns>The task that represents the scheduled work</returns>
public static Task ObserveWork(this PluginBase plugin, IAsyncBackgroundWork work, int delayMs = 0)
{
- return ObserveTask(plugin, () => work.DoWorkAsync(plugin.Log, plugin.UnloadToken), delayMs);
+ return ObserveWork(plugin, () => work.DoWorkAsync(plugin.Log, plugin.UnloadToken), delayMs);
}
/// <summary>
/// Registers an event to occur when the plugin is unloaded on a background thread
/// and will cause the Plugin.Unload() method to block until the event completes
/// </summary>
- /// <param name="pbase"></param>
+ /// <param name="plugin"></param>
/// <param name="callback">The method to call when the plugin is unloaded</param>
/// <returns>A task that represents the registered work</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static Task RegisterForUnload(this PluginBase pbase, Action callback)
+ public static Task RegisterForUnload(this PluginBase plugin, Action callback)
{
//Test status
- pbase.ThrowIfUnloaded();
+ plugin.ThrowIfUnloaded();
_ = callback ?? throw new ArgumentNullException(nameof(callback));
//Wait method
@@ -307,9 +273,238 @@ namespace VNLib.Plugins.Extensions.Loading
}
//Registaer the task to cause the plugin to wait
- return pbase.ObserveTask(() => WaitForUnload(pbase, callback));
+ return plugin.ObserveWork(() => WaitForUnload(plugin, callback));
+ }
+
+ /// <summary>
+ /// <para>
+ /// Gets or inializes a singleton service of the desired type.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncBackgroundWork"/> the <see cref="IAsyncBackgroundWork.DoWorkAsync(ILogProvider, System.Threading.CancellationToken)"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="plugin"></param>
+ /// <returns></returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static T GetOrCreateSingleton<T>(this PluginBase plugin)
+ {
+ //Add service to service continer
+ return GetOrCreateSingleton(plugin, CreateService<T>);
+ }
+
+ /// <summary>
+ /// <para>
+ /// Gets or inializes a singleton service of the desired type.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncBackgroundWork"/> the <see cref="IAsyncBackgroundWork.DoWorkAsync(ILogProvider, System.Threading.CancellationToken)"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="configName">Overrids the default configuration property name</param>
+ /// <returns>The configured service singleton</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static T GetOrCreateSingleton<T>(this PluginBase plugin, string configName)
+ {
+ //Add service to service continer
+ return GetOrCreateSingleton(plugin, (plugin) => CreateService<T>(plugin, configName));
+ }
+
+ /// <summary>
+ /// Configures the service asynchronously on the plugin's scheduler and returns a task
+ /// that represents the configuration work.
+ /// </summary>
+ /// <typeparam name="T">The service type</typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="service">The service to configure</param>
+ /// <param name="delayMs">The time in milliseconds to delay the configuration task</param>
+ /// <returns>A task that complets when the load operation completes</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static Task ConfigureServiceAsync<T>(this PluginBase plugin, T service, int delayMs = 0) where T : IAsyncConfigurable
+ {
+ //Register async load
+ return ObserveWork(plugin, () => service.ConfigureServiceAsync(plugin), delayMs);
+ }
+
+ /// <summary>
+ /// <para>
+ /// Creates and configures a new instance of the desired type and captures the configuration
+ /// information from the type.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncBackgroundWork"/> the <see cref="IAsyncBackgroundWork.DoWorkAsync(ILogProvider, System.Threading.CancellationToken)"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IDisposable"/> the <see cref="IDisposable.Dispose"/> method is called once when
+ /// the plugin is unloaded.
+ /// </para>
+ /// </summary>
+ /// <typeparam name="T">The service type</typeparam>
+ /// <param name="plugin"></param>
+ /// <returns>The a new instance configured service</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static T CreateService<T>(this PluginBase plugin)
+ {
+ if (plugin.HasConfigForType<T>())
+ {
+ IConfigScope config = plugin.GetConfigForType<T>();
+ return CreateService<T>(plugin, config);
+ }
+ else
+ {
+ return CreateService<T>(plugin, (IConfigScope?)null);
+ }
+ }
+
+ /// <summary>
+ /// <para>
+ /// Creates and configures a new instance of the desired type, with the configuration property name
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncBackgroundWork"/> the <see cref="IAsyncBackgroundWork.DoWorkAsync(ILogProvider, System.Threading.CancellationToken)"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// </summary>
+ /// <typeparam name="T">The service type</typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="configName">The configuration element name to pass to the new instance</param>
+ /// <returns>The a new instance configured service</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static T CreateService<T>(this PluginBase plugin, string configName)
+ {
+ IConfigScope config = plugin.GetConfig(configName);
+ return CreateService<T>(plugin, config);
+ }
+
+ /// <summary>
+ /// <para>
+ /// Creates and configures a new instance of the desired type, with the specified configuration scope
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// <para>
+ /// If the type derrives <see cref="IAsyncBackgroundWork"/> the <see cref="IAsyncBackgroundWork.DoWorkAsync(ILogProvider, System.Threading.CancellationToken)"/>
+ /// method is called once when the instance is loaded, and observed on the plugin scheduler.
+ /// </para>
+ /// </summary>
+ /// <typeparam name="T">The service type</typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="config">The configuration scope to pass directly to the new instance</param>
+ /// <returns>The a new instance configured service</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static T CreateService<T>(this PluginBase plugin, IConfigScope? config)
+ {
+ plugin.ThrowIfUnloaded();
+
+ Type serviceType = typeof(T);
+
+ T service;
+
+ //Determin configuration requirments
+ if (ConfigurationExtensions.ConfigurationRequired(serviceType) || config != null)
+ {
+ if(config == null)
+ {
+ ConfigurationExtensions.ThrowConfigNotFoundForType(serviceType);
+ }
+
+ //Get the constructor for required or available config
+ ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
+
+ //Make sure the constructor exists
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
+
+ //Call constructore
+ service = (T)constructor.Invoke(new object[2] { plugin, config });
+ }
+ else
+ {
+ //Get the constructor
+ ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase) });
+
+ //Make sure the constructor exists
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
+
+ //Call constructore
+ service = (T)constructor.Invoke(new object[1] { plugin });
+ }
+
+ Task? loading = null;
+
+ //If the service is async configurable, configure it
+ if (service is IAsyncConfigurable asc)
+ {
+#pragma warning disable CA5394 // Do not use insecure randomness
+ int randomDelay = Random.Shared.Next(1, 100);
+#pragma warning restore CA5394 // Do not use insecure randomness
+
+ //Register async load
+ loading = plugin.ConfigureServiceAsync(asc, randomDelay);
+ }
+
+ //Allow background work loading
+ if (service is IAsyncBackgroundWork bw)
+ {
+
+#pragma warning disable CA5394 // Do not use insecure randomness
+ int randomDelay = Random.Shared.Next(10, 200);
+#pragma warning restore CA5394 // Do not use insecure randomness
+
+ //If the instances supports async loading, dont start work until its loaded
+ if(loading != null)
+ {
+ _ = loading.ContinueWith(t => ObserveWork(plugin, bw, randomDelay), TaskScheduler.Default);
+ }
+ else
+ {
+ _ = ObserveWork(plugin, bw, randomDelay);
+ }
+ }
+
+ //register dispose cleanup
+ if (service is IDisposable disp)
+ {
+ _ = plugin.RegisterForUnload(disp.Dispose);
+ }
+
+ return service;
}
+
private sealed class PluginLocalCache
{
private readonly PluginBase _plugin;
@@ -348,52 +543,91 @@ namespace VNLib.Plugins.Extensions.Loading
}
}
- private sealed class SecretProvider : ISecretProvider
+ [ConfigurationName(PASSWORD_HASHING_KEY, Required = false)]
+ private sealed class SecretProvider : VnDisposeable, ISecretProvider, IAsyncConfigurable
{
private byte[]? _pepper;
private Exception? _error;
- ///<inheritdoc/>
- public int BufferSize => _error != null ? throw _error : _pepper?.Length ?? 0;
+ public SecretProvider(PluginBase plugin, IConfigScope config)
+ {
+ if(config.TryGetValue("args", out JsonElement el))
+ {
+ //Convert to dict
+ IReadOnlyDictionary<string, JsonElement> hashingArgs = el.EnumerateObject().ToDictionary(static k => k.Name, static v => v.Value);
+
+ //Get hashing arguments
+ uint saltLen = hashingArgs["salt_len"].GetUInt32();
+ uint hashLen = hashingArgs["hash_len"].GetUInt32();
+ uint timeCost = hashingArgs["time_cost"].GetUInt32();
+ uint memoryCost = hashingArgs["memory_cost"].GetUInt32();
+ uint parallelism = hashingArgs["parallelism"].GetUInt32();
+ //Load passwords
+ Passwords = new(this, (int)saltLen, timeCost, memoryCost, parallelism, hashLen);
+ }
+ else
+ {
+ Passwords = new(this);
+ }
+ }
- public ERRNO GetSecret(Span<byte> buffer)
+ public SecretProvider(PluginBase plugin)
{
- if(_error != null)
+ Passwords = new(this);
+ }
+
+
+ public PasswordHashing Passwords { get; }
+
+ ///<inheritdoc/>
+ public int BufferSize
+ {
+ get
{
- throw _error;
+ Check();
+ return _pepper!.Length;
}
+ }
+
+ public ERRNO GetSecret(Span<byte> buffer)
+ {
+ Check();
//Coppy pepper to buffer
_pepper.CopyTo(buffer);
//Return pepper length
return _pepper!.Length;
}
- public void LoadSecret(PluginBase pbase)
+ protected override void Check()
+ {
+ base.Check();
+ if(_error != null)
+ {
+ throw _error;
+ }
+ }
+
+ protected override void Free()
{
- _ = pbase.ObserveTask(() => LoadSecretInternal(pbase));
+ //Clear the pepper if set
+ MemoryUtil.InitializeBlock(_pepper.AsSpan());
}
- private async Task LoadSecretInternal(PluginBase pbase)
+ public async Task ConfigureServiceAsync(PluginBase plugin)
{
try
{
//Get the pepper from secret storage
- _pepper = await pbase.TryGetSecretAsync(PASSWORD_HASHING_KEY).ToBase64Bytes();
-
- //Regsiter cleanup
- _ = pbase.RegisterForUnload(Clear);
+ _pepper = await plugin.TryGetSecretAsync(PASSWORD_HASHING_KEY).ToBase64Bytes();
}
- catch(Exception ex)
+ catch (Exception ex)
{
//Store exception for re-propagation
_error = ex;
- }
- }
- public void Clear()
- {
- //Clear the pepper if set
- MemoryUtil.InitializeBlock(_pepper.AsSpan());
+ //Propagate exception to system
+ throw;
+ }
}
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs
index c1c6bb6..22686f0 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -24,22 +24,22 @@
using System;
using System.Linq;
-using System.Text.Json;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using VNLib.Plugins.Extensions.Loading.Events;
-using System.Net;
namespace VNLib.Plugins.Extensions.Loading.Routing
{
+
/// <summary>
/// Provides advanced QOL features to plugin loading
/// </summary>
public static class RoutingExtensions
{
private static readonly ConditionalWeakTable<IEndpoint, PluginBase?> _pluginRefs = new();
+
/// <summary>
/// Constructs and routes the specific endpoint type for the current plugin
@@ -69,13 +69,13 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
}
else
{
- ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IReadOnlyDictionary<string, JsonElement>) });
+ ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
//Make sure the constructor exists
_ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}");
//Get config variables for the endpoint
- IReadOnlyDictionary<string, JsonElement> conf = plugin.GetConfig(pluginConfigPathName);
+ IConfigScope conf = plugin.GetConfig(pluginConfigPathName);
//Create the new endpoint and pass the plugin instance along with the configuration object
endpoint = (T)constructor.Invoke(new object[] { plugin, conf });
@@ -126,10 +126,10 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
_ = _pluginRefs.TryGetValue(ep, out PluginBase? pBase);
return pBase ?? throw new InvalidOperationException("Endpoint was not dynamically routed");
}
-
- private static void ScheduleIntervals<T>(PluginBase plugin, T endpointInstance, Type epType, IReadOnlyDictionary<string, JsonElement>? endpointLocalConfig) where T : IEndpoint
+
+ private static void ScheduleIntervals<T>(PluginBase plugin, T endpointInstance, Type epType, IConfigScope? endpointLocalConfig) where T : IEndpoint
{
- //Get all methods that have the configureable async interval attribute specified
+ //Get all methods that have the configurable async interval attribute specified
IEnumerable<Tuple<ConfigurableAsyncIntervalAttribute, AsyncSchedulableCallback>> confIntervals = epType.GetMethods()
.Where(m => m.GetCustomAttribute<ConfigurableAsyncIntervalAttribute>() != null)
.Select(m => new Tuple<ConfigurableAsyncIntervalAttribute, AsyncSchedulableCallback>
@@ -169,5 +169,6 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
plugin.ScheduleInterval(interval.Item2, interval.Item1.Interval);
}
}
+
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs b/lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs
index bc42f3a..11f101f 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -22,14 +22,25 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System.Text.Json.Serialization;
+
namespace VNLib.Plugins.Extensions.Loading
{
public sealed class S3Config
{
+ [JsonPropertyName("server_address")]
public string? ServerAddress { get; init; }
+
+ [JsonPropertyName("access_key")]
public string? ClientId { get; init; }
+
+ [JsonPropertyName("bucket")]
public string? BaseBucket { get; init; }
+
+ [JsonPropertyName("use_ssl")]
public bool? UseSsl { get; init; }
+
+ [JsonPropertyName("region")]
public string? Region { get; init; }
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/UserLoading.cs b/lib/VNLib.Plugins.Extensions.Loading/src/UserLoading.cs
deleted file mode 100644
index b66bea3..0000000
--- a/lib/VNLib.Plugins.Extensions.Loading/src/UserLoading.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: UserLoading.cs
-*
-* UserLoading.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Loading 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 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Extensions.Loading 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.Collections.Generic;
-
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Users;
-
-namespace VNLib.Plugins.Extensions.Loading.Users
-{
- /// <summary>
- /// Contains extension methods for plugins to load the "users" system
- /// </summary>
- public static class UserLoading
- {
- public const string USER_CUSTOM_ASSEMBLY = "user_custom_asm";
- public const string DEFAULT_USER_ASM = "VNLib.Plugins.Essentials.Users.dll";
- public const string ONLOAD_METHOD_NAME = "OnPluginLoading";
-
-
- /// <summary>
- /// Gets or loads the plugin's ambient <see cref="IUserManager"/>, with the specified user-table name,
- /// or the default table name
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>The ambient <see cref="IUserManager"/> for the current plugin</returns>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IUserManager GetUserManager(this PluginBase plugin)
- {
- plugin.ThrowIfUnloaded();
- //Get stored or load
- return LoadingExtensions.GetOrCreateSingleton(plugin, LoadUsers);
- }
-
- private static IUserManager LoadUsers(PluginBase pbase)
- {
- //Try to load a custom user assembly for exporting IUserManager
- string? customAsm = pbase.PluginConfig.GetPropString(USER_CUSTOM_ASSEMBLY);
- //See if host config defined the path
- customAsm ??= pbase.HostConfig.GetPropString(USER_CUSTOM_ASSEMBLY);
- //Finally default
- customAsm ??= DEFAULT_USER_ASM;
-
- //Try to load a custom assembly
- AssemblyLoader<IUserManager> loader = pbase.LoadAssembly<IUserManager>(customAsm);
- try
- {
- //Try to get the onload method
- Action<object>? onLoadMethod = loader.TryGetMethod<Action<object>>(ONLOAD_METHOD_NAME);
-
- //Call the onplugin load method
- onLoadMethod?.Invoke(pbase);
-
- if (pbase.IsDebug())
- {
- pbase.Log.Verbose("Loading user manager from assembly {name}", loader.Resource.GetType().AssemblyQualifiedName);
- }
-
- //Return the loaded instance (may raise exception)
- return loader.Resource;
- }
- catch
- {
- loader.Dispose();
- throw;
- }
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs b/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs
new file mode 100644
index 0000000..a3d667d
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs
@@ -0,0 +1,138 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: UserManager.cs
+*
+* UserManager.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading 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 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading 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.Threading;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Logging;
+using VNLib.Plugins.Essentials.Users;
+
+namespace VNLib.Plugins.Extensions.Loading.Users
+{
+ /// <summary>
+ /// Provides a singleton <see cref="IUserManager"/> service that dynamically loads
+ /// a user manager for the plugin.
+ /// </summary>
+ [ConfigurationName("users", Required = false)]
+ public class UserManager : IUserManager
+ {
+ public const string USER_CUSTOM_ASSEMBLY = "custom_assembly";
+ public const string DEFAULT_USER_ASM = "VNLib.Plugins.Essentials.Users.dll";
+ public const string ONLOAD_METHOD_NAME = "OnPluginLoading";
+
+ private readonly IUserManager _dynamicLoader;
+
+ public UserManager(PluginBase plugin)
+ {
+ _dynamicLoader = LoadUserAssembly(plugin, DEFAULT_USER_ASM);
+ }
+
+ public UserManager(PluginBase plugin, IConfigScope config)
+ {
+ //Get the service configuration
+ string customAsm = config[USER_CUSTOM_ASSEMBLY].GetString() ?? DEFAULT_USER_ASM;
+ //Load the assembly
+ _dynamicLoader = LoadUserAssembly(plugin, customAsm);
+ }
+
+ private static IUserManager LoadUserAssembly(PluginBase plugin, string customAsm)
+ {
+ //Try to load a custom assembly
+ AssemblyLoader<IUserManager> loader = plugin.LoadAssembly<IUserManager>(customAsm);
+ try
+ {
+ //Try to get the onload method
+ Action<object>? onLoadMethod = loader.TryGetMethod<Action<object>>(ONLOAD_METHOD_NAME);
+
+ //Call the onplugin load method
+ onLoadMethod?.Invoke(plugin);
+
+ if (plugin.IsDebug())
+ {
+ plugin.Log.Debug("Loading user manager from assembly {name}", loader.Resource.GetType().AssemblyQualifiedName);
+ }
+
+ //Return the loaded instance (may raise exception)
+ return loader.Resource;
+ }
+ catch
+ {
+ loader.Dispose();
+ throw;
+ }
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<IUser> CreateUserAsync(string userid, string emailAddress, ulong privilages, PrivateString passHash, CancellationToken cancellation = default)
+ {
+ return _dynamicLoader.CreateUserAsync(userid, emailAddress, privilages, passHash, cancellation);
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<IUser?> GetUserAndPassFromEmailAsync(string emailAddress, CancellationToken cancellationToken = default)
+ {
+ return _dynamicLoader.GetUserAndPassFromEmailAsync(emailAddress, cancellationToken);
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<IUser?> GetUserAndPassFromIDAsync(string userid, CancellationToken cancellation = default)
+ {
+ return _dynamicLoader.GetUserAndPassFromIDAsync(userid, cancellation);
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<long> GetUserCountAsync(CancellationToken cancellation = default)
+ {
+ return _dynamicLoader.GetUserCountAsync(cancellation);
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<IUser?> GetUserFromEmailAsync(string emailAddress, CancellationToken cancellationToken = default)
+ {
+ return _dynamicLoader.GetUserFromEmailAsync(emailAddress, cancellationToken);
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<IUser?> GetUserFromIDAsync(string userId, CancellationToken cancellationToken = default)
+ {
+ return _dynamicLoader.GetUserFromIDAsync(userId, cancellationToken);
+ }
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task<ERRNO> UpdatePassAsync(IUser user, PrivateString newPass, CancellationToken cancellation = default)
+ {
+ return _dynamicLoader.UpdatePassAsync(user, newPass, cancellation);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj b/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj
index a660a7e..ca4113e 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/VNLib.Plugins.Extensions.Loading.csproj
@@ -4,11 +4,8 @@
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>VNLib.Plugins.Extensions.Loading</RootNamespace>
<AssemblyName>VNLib.Plugins.Extensions.Loading</AssemblyName>
- <Version>1.0.1.1</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Nullable>enable</Nullable>
- <SignAssembly>True</SignAssembly>
- <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
<AnalysisLevel>latest-all</AnalysisLevel>
</PropertyGroup>
@@ -18,6 +15,21 @@
<Authors>Vaughn Nugent</Authors>
</PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Extensions.Loading</Product>
+ <PackageId>VNLib.Plugins.Extensions.Loading</PackageId>
+ <Description>
+ An Essentials framework extension library for common loading/configuration/service operations. Enables rapid plugin
+ and service development.
+ </Description>
+ <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Loading</RepositoryUrl>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
<PrivateAssets>all</PrivateAssets>
@@ -27,7 +39,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="VaultSharp" Version="1.12.2.1" />
</ItemGroup>
@@ -37,4 +48,8 @@
<ProjectReference Include="..\..\..\..\..\core\lib\Utils\src\VNLib.Utils.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Routing\" />
+ </ItemGroup>
+
</Project>
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs b/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs
index da6650a..25e30ea 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -284,7 +284,7 @@ namespace VNLib.Plugins.Extensions.Loading
private static IVaultClient? TryGetVaultLoader(PluginBase pbase)
{
//Get vault config
- IReadOnlyDictionary<string, JsonElement>? conf = pbase.TryGetConfig(VAULT_OBJECT_NAME);
+ IConfigScope? conf = pbase.TryGetConfig(VAULT_OBJECT_NAME);
if (conf == null)
{
diff --git a/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj b/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj
index 2496138..826fe47 100644
--- a/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj
+++ b/lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj
@@ -4,21 +4,27 @@
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>VNLib.Plugins.Extensions.Validation</RootNamespace>
<AssemblyName>VNLib.Plugins.Extensions.Validation</AssemblyName>
- <Version>1.0.1.1</Version>
<Nullable>enable</Nullable>
- <SignAssembly>True</SignAssembly>
- <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
<AnalysisLevel>latest-all</AnalysisLevel>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Plugins.Extensions.Validation</Product>
+ <PackageId>VNLib.Plugins.Extensions.Validation</PackageId>
+ <Description>
+ An Essentials framework extension library for extending FluentValidation validators for common checks
+ including minium password validation.
+ </Description>
<Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
- <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Sessions</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/VNLib.Plugins.Extensions.Validation</RepositoryUrl>
</PropertyGroup>
+
<ItemGroup>
<PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
<PrivateAssets>all</PrivateAssets>
@@ -28,7 +34,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="FluentValidation" Version="11.4.0" />
+ <PackageReference Include="FluentValidation" Version="11.5.1" />
</ItemGroup>
<ItemGroup>