diff options
Diffstat (limited to 'lib')
9 files changed, 134 insertions, 29 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/ProtectedEntityExtensions.cs b/lib/VNLib.Plugins.Extensions.Data/src/ProtectedEntityExtensions.cs index dc23bf4..ea8d8cb 100644 --- a/lib/VNLib.Plugins.Extensions.Data/src/ProtectedEntityExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Data/src/ProtectedEntityExtensions.cs @@ -25,10 +25,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Collections.Generic; using VNLib.Utils; using VNLib.Plugins.Extensions.Data.Abstractions; + namespace VNLib.Plugins.Extensions.Data { public static class ProtectedEntityExtensions @@ -40,7 +42,7 @@ namespace VNLib.Plugins.Extensions.Data /// <param name="record">The record to update</param> /// <param name="userId">The userid of the record owner</param> /// <returns>A task that evaluates to the number of records modified</returns> - public static Task<ERRNO> UpdateAsync<TEntity>(this IDataStore<TEntity> store, TEntity record, string userId) where TEntity : class, IDbModel, IUserEntity + public static Task<ERRNO> UpdateUserRecordAsync<TEntity>(this IDataStore<TEntity> store, TEntity record, string userId) where TEntity : class, IDbModel, IUserEntity { record.UserId = userId; return store.UpdateAsync(record); @@ -53,7 +55,7 @@ namespace VNLib.Plugins.Extensions.Data /// <param name="record">The record to update</param> /// <param name="userId">The userid of the record owner</param> /// <returns>A task that evaluates to the number of records modified</returns> - public static Task<ERRNO> CreateAsync<TEntity>(this IDataStore<TEntity> store, TEntity record, string userId) where TEntity : class, IDbModel, IUserEntity + public static Task<ERRNO> CreateUserRecordAsync<TEntity>(this IDataStore<TEntity> store, TEntity record, string userId) where TEntity : class, IDbModel, IUserEntity { record.UserId = userId; return store.CreateAsync(record); @@ -66,21 +68,50 @@ namespace VNLib.Plugins.Extensions.Data /// <param name="key">The unique id of the entity</param> /// <param name="userId">The user's id that owns the resource</param> /// <returns>A task that resolves the entity or null if not found</returns> - public static Task<TEntity?> GetSingleAsync<TEntity>(this IDataStore<TEntity> store, string key, string userId) where TEntity : class, IDbModel, IUserEntity + public static Task<TEntity?> GetSingleUserRecordAsync<TEntity>(this IDataStore<TEntity> store, string key, string userId) where TEntity : class, IDbModel, IUserEntity { return store.GetSingleAsync(key, userId); } /// <summary> + /// Gets a page by its number offset constrained by its limit, + /// for the given user id + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <param name="store"></param> + /// <param name="collection">The collection to store found records</param> + /// <param name="userId">The user to get the page for</param> + /// <param name="page">The page offset</param> + /// <param name="limit">The record limit for the page</param> + /// <returns>A task that resolves the number of entities added to the collection</returns> + public static Task<int> GetUserPageAsync<TEntity>(this IPaginatedDataStore<TEntity> store, ICollection<TEntity> collection, string userId, int page, int limit) + where TEntity : class, IDbModel, IUserEntity + { + return store.GetPageAsync(collection, page, limit, userId); + } + + /// <summary> /// Deletes a single entiry by its ID only if it belongs to the speicifed user /// </summary> /// <param name="store"></param> /// <param name="key">The unique id of the entity</param> /// <param name="userId">The user's id that owns the resource</param> /// <returns>A task the resolves the number of eneities deleted (should evaluate to true or false)</returns> - public static Task<ERRNO> DeleteAsync<TEntity>(this IDataStore<TEntity> store, string key, string userId) where TEntity : class, IDbModel, IUserEntity + public static Task<ERRNO> DeleteUserRecordAsync<TEntity>(this IDataStore<TEntity> store, string key, string userId) where TEntity : class, IDbModel, IUserEntity { return store.DeleteAsync(key, userId); } + + /// <summary> + /// Gets the record count for the specified userId + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <param name="store"></param> + /// <param name="userId">The unique id of the user to query record count</param> + /// <returns>A task that resolves the number of records belonging to the specified user</returns> + public static Task<long> GetUserRecordCountAsync<TEntity>(this IDataStore<TEntity> store, string userId) where TEntity : class, IDbModel, IUserEntity + { + return store.GetCountAsync(userId); + } } } 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 bf09e6d..67310b6 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 @@ -37,8 +37,8 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.11" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.11" /> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.13" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.13" /> </ItemGroup> <ItemGroup> 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 a663946..f944cd6 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 @@ -28,9 +28,9 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.11" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.11" /> - <PackageReference Include="MySqlConnector" Version="2.2.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.13" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.13" /> + <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/Events/EventManagment.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs index fde67f7..bde6986 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Events/EventManagment.cs @@ -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.DeferTask(() => RunIntervalOnPluginScheduler(plugin, asyncCallback, interval, immediate)); + _ = plugin.ObserveTask(() => 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/IAsyncBackgroundWork.cs b/lib/VNLib.Plugins.Extensions.Loading/src/IAsyncBackgroundWork.cs new file mode 100644 index 0000000..9fb66a2 --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/IAsyncBackgroundWork.cs @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: LoadingExtensions.cs +* +* LoadingExtensions.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; +using VNLib.Utils.Logging; +using System.Threading; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// <summary> + /// Represents a low priority or long running work task to be done + /// and observed by a loaded plugin + /// </summary> + public interface IAsyncBackgroundWork + { + /// <summary> + /// Called when low priority work is ready to be run and its results + /// marshaled back to the plugin context + /// </summary> + /// <param name="pluginLog">The plugins default log provider</param> + /// <param name="exitToken">A token that signals when the plugin is unloading and work should be cancelled</param> + /// <returns>A task representing the low priority work to observed</returns> + Task DoWorkAsync(ILogProvider pluginLog, CancellationToken exitToken); + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs index 25dc4ec..743566d 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs @@ -38,6 +38,7 @@ using VNLib.Plugins.Essentials.Accounts; namespace VNLib.Plugins.Extensions.Loading { + /// <summary> /// Provides common loading (and unloading when required) extensions for plugins /// </summary> @@ -225,7 +226,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 DeferTask(this PluginBase plugin, Func<Task> asyncTask, int delayMs = 0) + public static async Task ObserveTask(this PluginBase plugin, Func<Task> asyncTask, int delayMs = 0) { /* * Motivation: @@ -266,6 +267,20 @@ 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 + /// </summary> + /// <param name="plugin"></param> + /// <param name="work">The work to be observed</param> + /// <param name="delayMs">The time (in milliseconds) to delay dispatching the work item</param> + /// <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); + } + /// <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 @@ -292,10 +307,9 @@ namespace VNLib.Plugins.Extensions.Loading } //Registaer the task to cause the plugin to wait - return pbase.DeferTask(() => WaitForUnload(pbase, callback)); + return pbase.ObserveTask(() => WaitForUnload(pbase, callback)); } - private sealed class PluginLocalCache { private readonly PluginBase _plugin; @@ -356,7 +370,7 @@ namespace VNLib.Plugins.Extensions.Loading public void LoadSecret(PluginBase pbase) { - _ = pbase.DeferTask(() => LoadSecretInternal(pbase)); + _ = pbase.ObserveTask(() => LoadSecretInternal(pbase)); } private async Task LoadSecretInternal(PluginBase pbase) diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs index f27b3b3..c1c6bb6 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using VNLib.Plugins.Extensions.Loading.Events; +using System.Net; namespace VNLib.Plugins.Extensions.Loading.Routing { @@ -50,42 +51,53 @@ namespace VNLib.Plugins.Extensions.Loading.Routing public static T Route<T>(this PluginBase plugin, string? pluginConfigPathName) where T : IEndpoint { Type endpointType = typeof(T); + + T endpoint; + //If the config attribute is not set, then ignore the config variables if (string.IsNullOrWhiteSpace(pluginConfigPathName)) { ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) }); + _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}"); + //Create the new endpoint and pass the plugin instance - T endpoint = (T)constructor.Invoke(new object[] { plugin }); + endpoint = (T)constructor.Invoke(new object[] { plugin }); + //Register event handlers for the endpoint ScheduleIntervals(plugin, endpoint, endpointType, null); - //Route the endpoint - plugin.Route(endpoint); - - //Store ref to plugin for endpoint - _pluginRefs.Add(endpoint, plugin); - - return endpoint; } else { ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IReadOnlyDictionary<string, JsonElement>) }); + //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); + //Create the new endpoint and pass the plugin instance along with the configuration object - T endpoint = (T)constructor.Invoke(new object[] { plugin, conf }); + endpoint = (T)constructor.Invoke(new object[] { plugin, conf }); + //Register event handlers for the endpoint ScheduleIntervals(plugin, endpoint, endpointType, conf); - //Route the endpoint - plugin.Route(endpoint); + } - //Store ref to plugin for endpoint - _pluginRefs.Add(endpoint, plugin); + //Route the endpoint + plugin.Route(endpoint); - return endpoint; + //Store ref to plugin for endpoint + _pluginRefs.Add(endpoint, plugin); + + //See if the endpoint is disposable + if (endpoint is IDisposable dis) + { + //Register dispose for unload + _ = plugin.RegisterForUnload(dis.Dispose); } + + return endpoint; } /// <summary> 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 c31ae1c..a660a7e 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 @@ -28,7 +28,7 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> - <PackageReference Include="VaultSharp" Version="1.7.1" /> + <PackageReference Include="VaultSharp" Version="1.12.2.1" /> </ItemGroup> <ItemGroup> diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs b/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs index d3bdf42..da6650a 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/VaultSecrets.cs @@ -78,6 +78,7 @@ namespace VNLib.Plugins.Extensions.Loading /// <exception cref="ObjectDisposedException"></exception> public static Task<SecretResult?> TryGetSecretAsync(this PluginBase plugin, string secretName) { + plugin.ThrowIfUnloaded(); //Get the secret from the config file raw string? rawSecret = TryGetSecretInternal(plugin, secretName); if (rawSecret == null) @@ -159,6 +160,7 @@ namespace VNLib.Plugins.Extensions.Loading /// <exception cref="ObjectDisposedException"></exception> public static Task<X509Certificate?> TryGetCertificateAsync(this PluginBase plugin, string secretName) { + plugin.ThrowIfUnloaded(); //Get the secret from the config file raw string? rawSecret = TryGetSecretInternal(plugin, secretName); if (rawSecret == null) |