aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-07-28 18:52:54 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-07-28 18:52:54 -0400
commit641bdbe75cb0128c09e27f1b92709c86574026ac (patch)
treeeb412bcb9fa0442d2e283c54d207204034fcbe11
parent1350c983c371fdd6a93596c8474345f9168284e1 (diff)
Squashed commit of the following:
commit 87887c0a45c84458e81fa38f7f4dca31faa49d7c Author: vnugent <public@vaughnnugent.com> Date: Sat Jul 27 22:41:17 2024 -0400 package updates commit d52772c010b10357d194239056d19c0d22d414fe Author: vnugent <public@vaughnnugent.com> Date: Sat Jul 27 20:50:12 2024 -0400 update secrets and remove deprecated and unused apis commit c8567e58dc1d4135da1f6cefa6fa66af5fcd7b19 Author: vnugent <public@vaughnnugent.com> Date: Mon Jul 15 19:05:01 2024 -0400 feat: Smiplify configuration helpers commit 640ee6760c07b628529e3160c16641773c76e800 Author: vnugent <public@vaughnnugent.com> Date: Thu Jul 4 23:02:56 2024 -0400 package updates commit db5747a20600a2e2c5e8d915cf0bdbe4ec6df6a2 Author: vnugent <public@vaughnnugent.com> Date: Fri Jun 21 17:08:16 2024 -0400 configuration validation updates commit 9bc24801735884e0c03aa00e83804448c466bdf2 Author: vnugent <public@vaughnnugent.com> Date: Sun Jun 16 13:16:18 2024 -0400 update plugins array instead of single path commit 1229ed75549de1c56aaee42c921acbd96c4d4c9b Author: vnugent <public@vaughnnugent.com> Date: Tue Jun 11 22:13:58 2024 -0400 feat: Stage some mvc stuff commit 35815081df2149741a6a79a880a57d63c5938a34 Author: vnugent <public@vaughnnugent.com> Date: Sun Jun 9 13:06:45 2024 -0400 package updates commit bcbe51bef546458cb7fee0d8f1dfd00cf936545a Author: vnugent <public@vaughnnugent.com> Date: Fri Jun 7 22:03:19 2024 -0400 feat: Allow S3Config type inheritence commit 2e55ac437f44eb5f9d66f7d7fd47b5670dedc2cb Merge: 27fb538 1350c98 Author: vnugent <public@vaughnnugent.com> Date: Wed May 22 15:29:47 2024 -0400 Merge branch 'master' into develop commit 27fb5382d80d9bcfb4c65974bbae20c5e7b8ccbc Author: vnugent <public@vaughnnugent.com> Date: Wed May 22 00:57:34 2024 -0400 feat: Vault environment vars commit 69f13e43dfdd8069459800ccc3039f45fc884814 Author: vnugent <public@vaughnnugent.com> Date: Wed May 15 22:04:43 2024 -0400 fix: #3 Defer vault loading until a secret actually needs it commit c848787d4830a73e9ba93898897282be2f3752f2 Author: vnugent <public@vaughnnugent.com> Date: Wed May 15 22:02:02 2024 -0400 package updates commit 21c6c85f540740ac29536a7091346a731aa85148 Author: vnugent <public@vaughnnugent.com> Date: Wed May 15 22:01:16 2024 -0400 fix: #3 Error raised when managed password type disposed commit 8e77289041349b16536497f48f0c0a4ec6fe30f5 Author: vnugent <public@vaughnnugent.com> Date: Thu May 2 15:44:42 2024 -0400 feat: #2 Middleware helpers, proj cleanup, fix sync secrets, vault client commit e0a5c85297516188e57b54d9b530b2482cb03eb0 Merge: a977dab 5ad520e Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 27 17:44:09 2024 -0400 Merge branch 'master' into develop commit a977dabef1dec915e00f755cb3ee3363aa9985f1 Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 27 17:26:35 2024 -0400 chore: package updates commit a2e2c3c4152d000b8df25c3c3fee14d491aab2c6 Merge: f03b727 87bfa83 Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 20 12:11:45 2024 -0400 Merge branch 'master' into develop commit f03b727d8f8e52f1dbd6293ea5c5a492c6d8e2da Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 20 12:02:07 2024 -0400 chore: Package updates
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/DbModelBase.cs5
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/SQL/DbExtensions.cs521
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/SQL/EnumerableTable.cs118
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlColumnNameAttribute.cs54
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlTableNameAttribute.cs40
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlVariable.cs58
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/SQL/TableManager.cs66
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/Blob.cs245
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobExtensions.cs67
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobStore.cs162
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWDecriptorCreationException.cs45
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageRemoveFailedException.cs44
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageUpdateFailedException.cs43
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/UndefinedBlobStateException.cs43
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs49
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs225
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageEntry.cs44
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs255
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj2
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj2
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs28
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs4
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Configuration/Validate.cs114
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs214
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationException.cs42
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs11
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs53
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointLogNameAttribute.cs41
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointPathAttribute.cs40
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs36
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs52
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs126
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs9
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs18
-rw-r--r--lib/VNLib.Plugins.Extensions.Validation/src/VNLib.Plugins.Extensions.Validation.csproj2
-rw-r--r--lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj2
-rw-r--r--lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj2
37 files changed, 666 insertions, 2216 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/DbModelBase.cs b/lib/VNLib.Plugins.Extensions.Data/src/DbModelBase.cs
index cdf763c..a396ab9 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/DbModelBase.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/DbModelBase.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -38,12 +38,15 @@ namespace VNLib.Plugins.Extensions.Data
{
///<inheritdoc/>
public abstract string Id { get; set; }
+
///<inheritdoc/>
[Timestamp]
[JsonIgnore]
public virtual byte[]? Version { get; set; }
+
///<inheritdoc/>
public abstract DateTime Created { get; set; }
+
///<inheritdoc/>
public abstract DateTime LastModified { get; set; }
}
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/SQL/DbExtensions.cs b/lib/VNLib.Plugins.Extensions.Data/src/SQL/DbExtensions.cs
deleted file mode 100644
index 2682ed2..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/SQL/DbExtensions.cs
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: DbExtensions.cs
-*
-* DbExtensions.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data;
-using System.Reflection;
-using System.Data.Common;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-
-using VNLib.Utils;
-using VNLib.Utils.Memory.Caching;
-
-namespace VNLib.Plugins.Extensions.Data.SQL
-{
- /// <summary>
- /// Provides basic extension methods for ADO.NET abstract classes
- /// for rapid development
- /// </summary>
- public static class DbExtensions
- {
- /*
- * Object rental for propery dictionaries used for custom result objects
- */
- private static ObjectRental<Dictionary<string, PropertyInfo>> DictStore { get; } = ObjectRental.Create<Dictionary<string, PropertyInfo>>(null, static dict => dict.Clear(), 20);
-
-
- /// <summary>
- /// Creates a new <see cref="DbParameter"/> configured for <see cref="ParameterDirection.Input"/> with the specified value
- /// and adds it to the command.
- /// </summary>
- /// <param name="cmd"></param>
- /// <param name="name">The parameter name</param>
- /// <param name="value">The value of the parameter</param>
- /// <param name="type">The <see cref="DbType"/> of the column</param>
- /// <param name="nullable">Are null types allowed in the value parameter</param>
- /// <returns>The created parameter</returns>
- public static DbParameter AddParameter<T>(this DbCommand cmd, string @name, T @value, DbType @type, bool @nullable = false)
- {
- //Create the new parameter from command
- DbParameter param = cmd.CreateParameter();
- //Set parameter variables
- param.ParameterName = name;
- param.Value = value;
- param.DbType = type;
- //Force non null mapping
- param.SourceColumnNullMapping = nullable;
- //Specify input parameter
- param.Direction = ParameterDirection.Input;
- //Add param to list
- cmd.Parameters.Add(param);
- return param;
- }
- /// <summary>
- /// Creates a new <see cref="DbParameter"/> configured for <see cref="ParameterDirection.Input"/> with the specified value
- /// and adds it to the command.
- /// </summary>
- /// <param name="cmd"></param>
- /// <param name="name">The parameter name</param>
- /// <param name="value">The value of the parameter</param>
- /// <param name="type">The <see cref="DbType"/> of the column</param>
- /// <param name="size">Size of the data value</param>
- /// <param name="nullable">Are null types allowed in the value parameter</param>
- /// <returns>The created parameter</returns>
- public static DbParameter AddParameter<T>(this DbCommand cmd, string @name, T @value, DbType @type, int @size, bool @nullable = false)
- {
- DbParameter param = AddParameter(cmd, name, value, type, nullable);
- //Set size parameter
- param.Size = size;
- return param;
- }
- /// <summary>
- /// Creates a new <see cref="DbParameter"/> configured for <see cref="ParameterDirection.Output"/> with the specified value
- /// and adds it to the command.
- /// </summary>
- /// <param name="cmd"></param>
- /// <param name="name">The parameter name</param>
- /// <param name="value">The value of the parameter</param>
- /// <param name="type">The <see cref="DbType"/> of the column</param>
- /// <param name="nullable">Are null types allowed in the value parameter</param>
- /// <returns>The created parameter</returns>
- public static DbParameter AddOutParameter<T>(this DbCommand cmd, string @name, T @value, DbType @type, bool @nullable = false)
- {
- //Create the new parameter from command
- DbParameter param = AddParameter(cmd, name, value, type, nullable);
- //Specify output parameter
- param.Direction = ParameterDirection.Output;
- return param;
- }
- /// <summary>
- /// Creates a new <see cref="DbParameter"/> configured for <see cref="ParameterDirection.Output"/> with the specified value
- /// and adds it to the command.
- /// </summary>
- /// <param name="cmd"></param>
- /// <param name="name">The parameter name</param>
- /// <param name="value">The value of the parameter</param>
- /// <param name="type">The <see cref="DbType"/> of the column</param>
- /// <param name="size">Size of the data value</param>
- /// <param name="nullable">Are null types allowed in the value parameter</param>
- /// <returns>The created parameter</returns>
- public static DbParameter AddOutParameter<T>(this DbCommand cmd, string @name, T @value, DbType @type, int @size, bool @nullable = false)
- {
- DbParameter param = AddOutParameter(cmd, name, value, type, nullable);
- //Set size parameter
- param.Size = size;
- return param;
- }
-
- /// <summary>
- /// Creates a new <see cref="DbCommand"/> for <see cref="CommandType.Text"/> with the specified command
- /// </summary>
- /// <param name="db"></param>
- /// <param name="cmdText">The command to run against the connection</param>
- /// <returns>The initalized <see cref="DbCommand"/></returns>
- public static DbCommand CreateTextCommand(this DbConnection db, string cmdText)
- {
- //Create the new command
- DbCommand cmd = db.CreateCommand();
- cmd.CommandText = cmdText;
- cmd.CommandType = CommandType.Text; //Specify text command
- return cmd;
- }
- /// <summary>
- /// Creates a new <see cref="DbCommand"/> for <see cref="CommandType.StoredProcedure"/> with the specified procedure name
- /// </summary>
- /// <param name="db"></param>
- /// <param name="procedureName">The name of the stored proecedure to execute</param>
- /// <returns>The initalized <see cref="DbCommand"/></returns>
- public static DbCommand CreateProcedureCommand(this DbConnection db, string procedureName)
- {
- //Create the new command
- DbCommand cmd = db.CreateCommand();
- cmd.CommandText = procedureName;
- cmd.CommandType = CommandType.StoredProcedure; //Specify stored procedure
- return cmd;
- }
-
- /// <summary>
- /// Creates a new <see cref="DbCommand"/> for <see cref="CommandType.Text"/> with the specified command
- /// on a given transaction
- /// </summary>
- /// <param name="db"></param>
- /// <param name="cmdText">The command to run against the connection</param>
- /// <param name="transaction">The transaction to execute on</param>
- /// <returns>The initalized <see cref="DbCommand"/></returns>
- public static DbCommand CreateTextCommand(this DbConnection db, string cmdText, DbTransaction transaction)
- {
- return CreateCommand(db, transaction, CommandType.Text, cmdText);
- }
- /// <summary>
- /// Shortcut to create a command on a transaction with the specifed command type and command
- /// </summary>
- /// <param name="db"></param>
- /// <param name="transaction">The transaction to complete the operation on</param>
- /// <param name="type">The command type</param>
- /// <param name="command">The command to execute</param>
- /// <returns>The intialized db command</returns>
- public static DbCommand CreateCommand(this DbConnection db, DbTransaction transaction, CommandType type, string command)
- {
- //Create the new command
- DbCommand cmd = db.CreateCommand();
- cmd.Transaction = transaction;
- cmd.CommandText = command;
- cmd.CommandType = type;
- return cmd;
- }
- /// <summary>
- /// Creates a new <see cref="DbCommand"/> for <see cref="CommandType.StoredProcedure"/> with the specified procedure name
- /// </summary>
- /// <param name="db"></param>
- /// <param name="procedureName">The name of the stored proecedure to execute</param>
- /// <param name="transaction">The transaction to execute on</param>
- /// <returns>The initalized <see cref="DbCommand"/></returns>
- public static DbCommand CreateProcedureCommand(this DbConnection db, string procedureName, DbTransaction transaction)
- {
- return CreateCommand(db, transaction, CommandType.StoredProcedure, procedureName);
- }
-
- /// <summary>
- /// Reads all available rows from the reader, adapts columns to public properties with <see cref="SqlColumnName"/>
- /// attributes, and adds them to the collection
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="reader"></param>
- /// <param name="container">The container to write created objects to</param>
- /// <returns>The number of objects created and written to the collection</returns>
- public static int GetAllObjects<T>(this DbDataReader reader, ICollection<T> container) where T : new()
- {
- //make sure its worth collecting object meta
- if (!reader.HasRows)
- {
- return 0;
- }
- Type objectType = typeof(T);
- //Rent a dict of properties that have the column attribute set so we can load the proper results
- Dictionary<string, PropertyInfo> avialbleProps = DictStore.Rent();
- //Itterate through public properties
- foreach (PropertyInfo prop in objectType.GetProperties())
- {
- //try to get the column name attribute of the propery
- SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
- //Attribute is valid and coumn name is not empty
- if (!string.IsNullOrWhiteSpace(colAtt?.ColumnName))
- {
- //Store the property for later
- avialbleProps[colAtt.ColumnName] = prop;
- }
- }
- //Get the column schema
- ReadOnlyCollection<DbColumn> columns = reader.GetColumnSchema();
- int count = 0;
- //Read
- while (reader.Read())
- {
- //Create the new object
- T ret = new();
- //Iterate through columns
- foreach (DbColumn col in columns)
- {
- //Get the propery if its specified by its column-name attribute
- if (avialbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop))
- {
- //make sure the column has a value
- if (col.ColumnOrdinal.HasValue)
- {
- //Get the object
- object val = reader.GetValue(col.ColumnOrdinal.Value);
- //Set check if the row is DB null, if so set it, otherwise set the value
- prop.SetValue(ret, Convert.IsDBNull(val) ? null : val);
- }
- }
- }
- //Add the object to the collection
- container.Add(ret);
- //Increment count
- count++;
- }
- //return dict (if an error occurs, just let the dict go and create a new one next time, no stress setting up a try/finally block)
- DictStore.Return(avialbleProps);
- return count;
- }
- /// <summary>
- /// Reads all available rows from the reader, adapts columns to public properties with <see cref="SqlColumnName"/>
- /// attributes, and adds them to the collection
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="reader"></param>
- /// <param name="container">The container to write created objects to</param>
- /// <returns>The number of objects created and written to the collection</returns>
- public static async ValueTask<int> GetAllObjectsAsync<T>(this DbDataReader reader, ICollection<T> container) where T : new()
- {
- //make sure its worth collecting object meta
- if (!reader.HasRows)
- {
- return 0;
- }
- Type objectType = typeof(T);
- //Rent a dict of properties that have the column attribute set so we can load the proper results
- Dictionary<string, PropertyInfo> avialbleProps = DictStore.Rent();
- //Itterate through public properties
- foreach (PropertyInfo prop in objectType.GetProperties())
- {
- //try to get the column name attribute of the propery
- SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
- //Attribute is valid and coumn name is not empty
- if (!string.IsNullOrWhiteSpace(colAtt?.ColumnName))
- {
- //Store the property for later
- avialbleProps[colAtt.ColumnName] = prop;
- }
- }
- //Get the column schema
- ReadOnlyCollection<DbColumn> columns = await reader.GetColumnSchemaAsync();
- int count = 0;
- //Read
- while (await reader.ReadAsync())
- {
- //Create the new object
- T ret = new();
- //Iterate through columns
- foreach (DbColumn col in columns)
- {
- //Get the propery if its specified by its column-name attribute
- if (avialbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop))
- {
- //make sure the column has a value
- if (col.ColumnOrdinal.HasValue)
- {
- //Get the object
- object val = reader.GetValue(col.ColumnOrdinal.Value);
- //Set check if the row is DB null, if so set it, otherwise set the value
- prop.SetValue(ret, Convert.IsDBNull(val) ? null : val);
- }
- }
- }
- //Add the object to the collection
- container.Add(ret);
- //Increment count
- count++;
- }
- //return dict (if an error occurs, just let the dict go and create a new one next time, no stress setting up a try/finally block)
- DictStore.Return(avialbleProps);
- return count;
- }
- /// <summary>
- /// Reads the first available row from the reader, adapts columns to public properties with <see cref="SqlColumnName"/>
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="reader"></param>
- /// <returns>The created object, or default if no rows are available</returns>
- public static T? GetFirstObject<T>(this DbDataReader reader) where T : new()
- {
- //make sure its worth collecting object meta
- if (!reader.HasRows)
- {
- return default;
- }
- //Get the object type
- Type objectType = typeof(T);
- //Get the column schema
- ReadOnlyCollection<DbColumn> columns = reader.GetColumnSchema();
- //Read
- if (reader.Read())
- {
- //Rent a dict of properties that have the column attribute set so we can load the proper results
- Dictionary<string, PropertyInfo> availbleProps = DictStore.Rent();
- //Itterate through public properties
- foreach (PropertyInfo prop in objectType.GetProperties())
- {
- //try to get the column name attribute of the propery
- SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
- //Attribute is valid and coumn name is not empty
- if (colAtt != null && !string.IsNullOrWhiteSpace(colAtt.ColumnName))
- {
- //Store the property for later
- availbleProps[colAtt.ColumnName] = prop;
- }
- }
- //Create the new object
- T ret = new();
- //Iterate through columns
- foreach (DbColumn col in columns)
- {
- //Get the propery if its specified by its column-name attribute
- if (availbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop) && col.ColumnOrdinal.HasValue)
- {
- //Get the object
- object val = reader.GetValue(col.ColumnOrdinal.Value);
- //Set check if the row is DB null, if so set it, otherwise set the value
- prop.SetValue(ret, Convert.IsDBNull(val) ? null : val);
- }
- }
- //Return dict, no stress if error occurs, the goal is lower overhead
- DictStore.Return(availbleProps);
- //Return the new object
- return ret;
- }
- return default;
- }
- /// <summary>
- /// Reads the first available row from the reader, adapts columns to public properties with <see cref="SqlColumnName"/>
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="reader"></param>
- /// <returns>The created object, or default if no rows are available</returns>
- public static async Task<T?> GetFirstObjectAsync<T>(this DbDataReader reader) where T : new()
- {
- //Read
- if (await reader.ReadAsync())
- {
- //Get the object type
- Type objectType = typeof(T);
- //Get the column schema
- ReadOnlyCollection<DbColumn> columns = await reader.GetColumnSchemaAsync();
- //Rent a dict of properties that have the column attribute set so we can load the proper results
- Dictionary<string, PropertyInfo> availbleProps = DictStore.Rent();
- //Itterate through public properties
- foreach (PropertyInfo prop in objectType.GetProperties())
- {
- //try to get the column name attribute of the propery
- SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
- //Attribute is valid and coumn name is not empty
- if (colAtt != null && !string.IsNullOrWhiteSpace(colAtt.ColumnName))
- {
- //Store the property for later
- availbleProps[colAtt.ColumnName] = prop;
- }
- }
- //Create the new object
- T ret = new();
- //Iterate through columns
- foreach (DbColumn col in columns)
- {
- //Get the propery if its specified by its column-name attribute
- if (availbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop) && col.ColumnOrdinal.HasValue)
- {
- //Get the object
- object val = reader.GetValue(col.ColumnOrdinal.Value);
- //Set check if the row is DB null, if so set it, otherwise set the value
- prop.SetValue(ret, Convert.IsDBNull(val) ? null : val);
- }
- }
- //Return dict, no stress if error occurs, the goal is lower overhead
- DictStore.Return(availbleProps);
- //Return the new object
- return ret;
- }
- return default;
- }
- /// <summary>
- /// Executes a nonquery operation with the specified command using the object properties set with the
- /// <see cref="SqlVariableAttribute"/> attributes
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="cmd"></param>
- /// <param name="obj">The object containing the <see cref="SqlVariableAttribute"/> properties to write to command variables</param>
- /// <returns>The number of rows affected</returns>
- /// <exception cref="TypeLoadException"></exception>
- /// <exception cref="ArgumentException"></exception>
- /// <exception cref="NotSupportedException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="AmbiguousMatchException"></exception>
- /// <exception cref="TargetInvocationException"></exception>
- public static ERRNO ExecuteNonQuery<T>(this DbCommand cmd, T obj) where T : notnull
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
- //Get the objec type
- Type objtype = typeof(T);
- //Itterate through public properties
- foreach (PropertyInfo prop in objtype.GetProperties())
- {
- //try to get the variable attribute of the propery
- SqlVariableAttribute varprops = prop.GetCustomAttribute<SqlVariableAttribute>(true);
- //This property is an sql variable, so lets add it
- if (varprops == null)
- {
- continue;
- }
- //If the command type is text, then make sure the variable is actually in the command, if not, ignore it
- if (cmd.CommandType != CommandType.Text || cmd.CommandText.Contains(varprops.VariableName))
- {
- //Add the parameter to the command list
- cmd.AddParameter(varprops.VariableName, prop.GetValue(obj), varprops.DataType, varprops.Size, varprops.IsNullable).Direction = varprops.Direction;
- }
- }
- //Prepare the sql statement
- cmd.Prepare();
- //Exect the query and return the results
- return cmd.ExecuteNonQuery();
- }
- /// <summary>
- /// Executes a nonquery operation with the specified command using the object properties set with the
- /// <see cref="SqlVariable"/> attributes
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="cmd"></param>
- /// <param name="obj">The object containing the <see cref="SqlVariable"/> properties to write to command variables</param>
- /// <returns>The number of rows affected</returns>
- /// <exception cref="TypeLoadException"></exception>
- /// <exception cref="ArgumentException"></exception>
- /// <exception cref="NotSupportedException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="AmbiguousMatchException"></exception>
- /// <exception cref="TargetInvocationException"></exception>
- public static async Task<ERRNO> ExecuteNonQueryAsync<T>(this DbCommand cmd, T obj) where T : notnull
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
- //Get the objec type
- Type objtype = typeof(T);
- //Itterate through public properties
- foreach (PropertyInfo prop in objtype.GetProperties())
- {
- //try to get the variable attribute of the propery
- SqlVariableAttribute? varprops = prop.GetCustomAttribute<SqlVariableAttribute>(true);
- //This property is an sql variable, so lets add it
- if (varprops == null)
- {
- continue;
- }
- //If the command type is text, then make sure the variable is actually in the command, if not, ignore it
- if (cmd.CommandType != CommandType.Text || cmd.CommandText.Contains(varprops.VariableName))
- {
- //Add the parameter to the command list
- cmd.AddParameter(varprops.VariableName, prop.GetValue(obj), varprops.DataType, varprops.Size, varprops.IsNullable).Direction = varprops.Direction;
- }
- }
- //Prepare the sql statement
- await cmd.PrepareAsync();
- //Exect the query and return the results
- return await cmd.ExecuteNonQueryAsync();
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/SQL/EnumerableTable.cs b/lib/VNLib.Plugins.Extensions.Data/src/SQL/EnumerableTable.cs
deleted file mode 100644
index 0d289b6..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/SQL/EnumerableTable.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: EnumerableTable.cs
-*
-* EnumerableTable.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data;
-using System.Threading;
-using System.Data.Common;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-
-namespace VNLib.Plugins.Extensions.Data.SQL
-{
- /// <summary>
- /// A base class for client side async enumerable SQL queries
- /// </summary>
- /// <typeparam name="T">The entity type</typeparam>
- public abstract class EnumerableTable<T> : TableManager, IAsyncEnumerable<T>
- {
- const string DEFAULT_ENUM_STATMENT = "SELECT *\r\nFROM @table\r\n;";
-
- public EnumerableTable(Func<DbConnection> factory, string tableName) : base(factory, tableName)
- {
- //Build the default select all statment
- Enumerate = DEFAULT_ENUM_STATMENT.Replace("@table", tableName);
- }
- public EnumerableTable(Func<DbConnection> factory) : base(factory)
- { }
-
- /// <summary>
- /// The command that will be run against the database to return rows for enumeration
- /// </summary>
- protected string Enumerate { get; set; }
-
- /// <summary>
- /// The isolation level to use when creating the transaction during enumerations
- /// </summary>
- protected IsolationLevel TransactionIsolationLevel { get; set; } = IsolationLevel.ReadUncommitted;
-
- IAsyncEnumerator<T> IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken cancellationToken)
- {
- return GetAsyncEnumerator(cancellationToken: cancellationToken);
- }
-
- /// <summary>
- /// Transforms a row from the <paramref name="reader"/> into the item type
- /// to be returned when yielded.
- /// </summary>
- /// <param name="reader">The reader to get the item data from</param>
- /// <param name="cancellationToken">A token to cancel the operation</param>
- /// <returns>A task that returns the transformed item</returns>
- /// <remarks>The <paramref name="reader"/> position is set before this method is invoked</remarks>
- protected abstract Task<T> GetItemAsync(DbDataReader reader, CancellationToken cancellationToken);
- /// <summary>
- /// Invoked when an item is no longer in the enumerator scope, in the enumeration process.
- /// </summary>
- /// <param name="item">The item to cleanup</param>
- /// <param name="cancellationToken">A token to cancel the operation</param>
- /// <returns>A ValueTask that represents the cleanup process</returns>
- protected abstract ValueTask CleanupItemAsync(T item, CancellationToken cancellationToken);
-
- /// <summary>
- /// Gets an <see cref="IAsyncEnumerator{T}"/> to enumerate items within the backing store.
- /// </summary>
- /// <param name="closeItems">Cleanup items after each item is enumerated and the enumeration scope has
- /// returned to the enumerator</param>
- /// <param name="cancellationToken">A token to cancel the enumeration</param>
- /// <returns>A <see cref="IAsyncEnumerator{T}"/> to enumerate records within the store</returns>
- public virtual async IAsyncEnumerator<T> GetAsyncEnumerator(bool closeItems = true, CancellationToken cancellationToken = default)
- {
- await using DbConnection db = GetConnection();
- await db.OpenAsync(cancellationToken);
- await using DbTransaction transaction = await db.BeginTransactionAsync(cancellationToken);
- //Start the enumeration command
- await using DbCommand cmd = db.CreateTextCommand(Enumerate, transaction);
- await cmd.PrepareAsync(cancellationToken);
- await using DbDataReader reader = await cmd.ExecuteReaderAsync(cancellationToken);
- //loop through results and transform each element
- while (reader.Read())
- {
- //get the item
- T item = await GetItemAsync(reader, cancellationToken);
- try
- {
- yield return item;
- }
- finally
- {
- if (closeItems)
- {
- //Cleanup the item
- await CleanupItemAsync(item, cancellationToken);
- }
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlColumnNameAttribute.cs b/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlColumnNameAttribute.cs
deleted file mode 100644
index f7628d6..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlColumnNameAttribute.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: SqlColumnNameAttribute.cs
-*
-* SqlColumnNameAttribute.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data.SQL
-{
- /// <summary>
- /// Property attribute that specifies the property represents an SQL column in the database
- /// </summary>
- [AttributeUsage(AttributeTargets.Property)]
- public sealed class SqlColumnNameAttribute : Attribute
- {
- public bool Nullable { get; }
- public bool Unique { get; }
- public bool PrimaryKey { get; }
- public string ColumnName { get; }
- /// <summary>
- /// Specifies the property is an SQL column name
- /// </summary>
- /// <param name="columnName">Name of the SQL column</param>
- /// <param name="primaryKey"></param>
- /// <param name="nullable"></param>
- /// <param name="unique"></param>
- public SqlColumnNameAttribute(string columnName, bool primaryKey = false, bool nullable = true, bool unique = false)
- {
- this.ColumnName = columnName;
- this.PrimaryKey = primaryKey;
- this.Nullable = nullable;
- this.Unique = unique;
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlTableNameAttribute.cs b/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlTableNameAttribute.cs
deleted file mode 100644
index 9668e91..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlTableNameAttribute.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: SqlTableNameAttribute.cs
-*
-* SqlTableNameAttribute.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data.SQL
-{
-
- /// <summary>
- /// Allows a type to declare itself as a <see cref="System.Data.DataTable"/> with the specified name
- /// </summary>
- [AttributeUsage(AttributeTargets.Class, AllowMultiple =false, Inherited = true)]
- public sealed class SqlTableNameAttribute : Attribute
- {
- public string TableName { get; }
-
- public SqlTableNameAttribute(string tableName) => TableName = tableName;
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlVariable.cs b/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlVariable.cs
deleted file mode 100644
index ef2e170..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/SQL/SqlVariable.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: SqlVariable.cs
-*
-* SqlVariable.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data;
-
-namespace VNLib.Plugins.Extensions.Data.SQL
-{
- /// <summary>
- /// Property attribute that specifies the property is to be used for a given command variable
- /// </summary>
- [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
- public sealed class SqlVariableAttribute : Attribute
- {
- public string VariableName { get; }
- public DbType DataType { get; }
- public ParameterDirection Direction { get; }
- public int Size { get; }
- public bool IsNullable { get; }
- /// <summary>
- /// Specifies the property to be used as an SQL variable
- /// </summary>
- /// <param name="variableName">Sql statement variable this property will substitute</param>
- /// <param name="dataType">The sql data the property will represent</param>
- /// <param name="direction">Data direction during execution</param>
- /// <param name="size">Column size</param>
- /// <param name="isNullable">Is this property allowed to be null</param>
- public SqlVariableAttribute(string variableName, DbType dataType, ParameterDirection direction, int size, bool isNullable)
- {
- this.VariableName = variableName;
- this.DataType = dataType;
- this.Direction = direction;
- this.Size = size;
- this.IsNullable = isNullable;
- }
- }
-}
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/SQL/TableManager.cs b/lib/VNLib.Plugins.Extensions.Data/src/SQL/TableManager.cs
deleted file mode 100644
index da1d8a7..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/SQL/TableManager.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: TableManager.cs
-*
-* TableManager.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data.Common;
-
-namespace VNLib.Plugins.Extensions.Data.SQL
-{
- /// <summary>
- /// A class that contains basic structures for interacting with an SQL driven database
- /// </summary>
- public abstract class TableManager
- {
- private readonly Func<DbConnection> Factory;
- protected string Insert { get; set; }
- protected string Select { get; set; }
- protected string Update { get; set; }
- protected string Delete { get; set; }
-
- /// <summary>
- /// The name of the table specified during initialized
- /// </summary>
- protected string TableName { get; }
-
- protected TableManager(Func<DbConnection> factory, string tableName)
- {
- this.Factory = factory ?? throw new ArgumentNullException(nameof(factory));
- this.TableName = !string.IsNullOrWhiteSpace(tableName) ? tableName : throw new ArgumentNullException(nameof(tableName));
- }
-
- protected TableManager(Func<DbConnection> factory)
- {
- this.Factory = factory ?? throw new ArgumentNullException(nameof(factory));
- this.TableName = "";
- }
- /// <summary>
- /// Opens a new <see cref="DbConnection"/> by invoking the factory callback method
- /// </summary>
- /// <returns>The open connection</returns>
- protected DbConnection GetConnection()
- {
- return Factory();
- }
- }
-}
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Blob.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/Blob.cs
deleted file mode 100644
index 0edf653..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Blob.cs
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: Blob.cs
-*
-* Blob.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Runtime.Versioning;
-
-using VNLib.Utils;
-using VNLib.Utils.IO;
-using VNLib.Utils.Async;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
- /// <summary>
- /// Represents a stream of arbitrary binary data
- /// </summary>
- public class Blob : BackingStream<FileStream>, IObjectStorage, IAsyncExclusiveResource
- {
- protected readonly LWStorageDescriptor Descriptor;
-
- /// <summary>
- /// The current blob's unique ID
- /// </summary>
- public string BlobId => Descriptor.DescriptorID;
- /// <summary>
- /// A value indicating if the <see cref="Blob"/> has been modified
- /// </summary>
- public bool Modified { get; protected set; }
- /// <summary>
- /// A valid indicating if the blob was flagged for deletiong
- /// </summary>
- public bool Deleted { get; protected set; }
-
- /// <summary>
- /// The name of the file (does not change the actual file system name)
- /// </summary>
- public string Name
- {
- get => Descriptor.GetName();
- set => Descriptor.SetName(value);
- }
- /// <summary>
- /// The UTC time the <see cref="Blob"/> was last modified
- /// </summary>
- public DateTimeOffset LastWriteTimeUtc => Descriptor.LastModified;
- /// <summary>
- /// The UTC time the <see cref="Blob"/> was created
- /// </summary>
- public DateTimeOffset CreationTimeUtc => Descriptor.Created;
-
- internal Blob(LWStorageDescriptor descriptor, in FileStream file)
- {
- this.Descriptor = descriptor;
- base.BaseStream = file;
- }
-
- /// <summary>
- /// Prevents other processes from reading from or writing to the <see cref="Blob"/>
- /// </summary>
- /// <param name="position">The begining position of the range to lock</param>
- /// <param name="length">The range to be locked</param>
- /// <exception cref="IOException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <exception cref="ArgumentOutOfRangeException"></exception>
- [UnsupportedOSPlatform("ios")]
- [UnsupportedOSPlatform("macos")]
- [UnsupportedOSPlatform("tvos")]
- public void Lock(long position, long length) => BaseStream.Lock(position, length);
- /// <summary>
- /// Prevents other processes from reading from or writing to the <see cref="Blob"/>
- /// </summary>
- /// <exception cref="IOException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <exception cref="ArgumentOutOfRangeException"></exception>
- [UnsupportedOSPlatform("ios")]
- [UnsupportedOSPlatform("macos")]
- [UnsupportedOSPlatform("tvos")]
- public void Lock() => BaseStream.Lock(0, BaseStream.Length);
- /// <summary>
- /// Allows access by other processes to all or part of the <see cref="Blob"/> that was previously locked
- /// </summary>
- /// <param name="position">The begining position of the range to unlock</param>
- /// <param name="length">The range to be unlocked</param>
- /// <exception cref="ArgumentOutOfRangeException"></exception>
- [UnsupportedOSPlatform("ios")]
- [UnsupportedOSPlatform("macos")]
- [UnsupportedOSPlatform("tvos")]
- public void Unlock(long position, long length) => BaseStream.Unlock(position, length);
- /// <summary>
- /// Allows access by other processes to the entire <see cref="Blob"/>
- /// </summary>
- /// <exception cref="ArgumentOutOfRangeException"></exception>
- [UnsupportedOSPlatform("ios")]
- [UnsupportedOSPlatform("macos")]
- [UnsupportedOSPlatform("tvos")]
- public void Unlock() => BaseStream.Unlock(0, BaseStream.Length);
- ///<inheritdoc/>
- public override void SetLength(long value)
- {
- base.SetLength(value);
- //Set modified flag
- Modified |= true;
- }
-
- /*
- * Capture on-write calls to set the modified flag
- */
- ///<inheritdoc/>
- protected override void OnWrite(int count) => Modified |= true;
-
- T IObjectStorage.GetObject<T>(string key) => ((IObjectStorage)Descriptor).GetObject<T>(key);
- void IObjectStorage.SetObject<T>(string key, T obj) => ((IObjectStorage)Descriptor).SetObject(key, obj);
-
- public string this[string index]
- {
- get => Descriptor[index];
- set => Descriptor[index] = value;
- }
-
-
- /// <summary>
- /// Marks the file for deletion and will be deleted when the <see cref="Blob"/> is disposed
- /// </summary>
- public void Delete()
- {
- //Set deleted flag
- Deleted |= true;
- Descriptor.Delete();
- }
- ///<inheritdoc/>
- public bool IsReleased => Descriptor.IsReleased;
-
-
- /// <summary>
- /// <para>
- /// If the <see cref="Blob"/> was opened with writing enabled,
- /// and file was modified, changes are flushed to the backing store
- /// and the stream is set to readonly.
- /// </para>
- /// <para>
- /// If calls to this method succeed the stream is placed into a read-only mode
- /// which will cause any calls to Write to throw a <see cref="NotSupportedException"/>
- /// </para>
- /// </summary>
- /// <returns>A <see cref="ValueTask"/> that may be awaited until the operation completes</returns>
- /// <remarks>
- /// This method may be called to avoid flushing changes to the backing store
- /// when the <see cref="Blob"/> is disposed (i.e. lifetime is manged outside of the desired scope)
- /// </remarks>
- /// <exception cref="IOException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <exception cref="InvalidOperationException"></exception>
- public async ValueTask FlushChangesAndSetReadonlyAsync()
- {
- if (Deleted)
- {
- throw new InvalidOperationException("The blob has been deleted and must be closed!");
- }
- if (Modified)
- {
- //flush the base stream
- await BaseStream.FlushAsync();
- //Update the file length in the store
- Descriptor.SetLength(BaseStream.Length);
- }
- //flush changes, this will cause the dispose method to complete synchronously when closing
- await Descriptor.WritePendingChangesAsync();
- //Clear modified flag
- Modified = false;
- //Set to readonly mode
- base.ForceReadOnly = true;
- }
-
-
- /*
- * Override the dispose async to manually dispose the
- * base stream and avoid the syncrhonous (OnClose)
- * method and allow awaiting the descriptor release
- */
- ///<inheritdoc/>
- public override async ValueTask DisposeAsync()
- {
- await ReleaseAsync();
- GC.SuppressFinalize(this);
- }
- ///<inheritdoc/>
- public async ValueTask ReleaseAsync(CancellationToken cancellation = default)
- {
- try
- {
- //Check for deleted
- if (Deleted)
- {
- //Dispose the base stream explicitly
- await BaseStream.DisposeAsync();
- //Try to delete the file
- File.Delete(BaseStream.Name);
- }
- //Check to see if the file was modified
- else if (Modified)
- {
- //Set the file size in bytes
- Descriptor.SetLength(BaseStream.Length);
- }
- }
- catch
- {
- //Set the error flag
- Descriptor.IsError(true);
- //propagate the exception
- throw;
- }
- finally
- {
- //Dispose the stream
- await BaseStream.DisposeAsync();
- //Release the descriptor
- await Descriptor.ReleaseAsync(cancellation);
- }
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobExtensions.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobExtensions.cs
deleted file mode 100644
index 425645c..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobExtensions.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: BlobExtensions.cs
-*
-* BlobExtensions.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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 VNLib.Utils;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
- public static class BlobExtensions
- {
- public const string USER_ID_ENTRY = "__.uid";
- public const string VERSION_ENTRY = "__.vers";
-
- private const string FILE_SIZE = "__.size";
- private const string FILE_NAME = "__.name";
- private const string ERROR_FLAG = "__.err";
-
- public static string GetUserId(this Blob blob) => blob[USER_ID_ENTRY];
- /// <summary>
- /// Gets the <see cref="Version"/> stored in the current <see cref="Blob"/>
- /// </summary>
- /// <returns>The sored version if previously set, thows otherwise</returns>
- /// <exception cref="FormatException"></exception>
- public static Version GetVersion(this Blob blob) => Version.Parse(blob[VERSION_ENTRY]);
- /// <summary>
- /// Sets a <see cref="Version"/> for the current <see cref="Blob"/>
- /// </summary>
- /// <param name="blob"></param>
- /// <param name="version">The <see cref="Version"/> of the <see cref="Blob"/></param>
- public static void SetVersion(this Blob blob, Version version) => blob[VERSION_ENTRY] = version.ToString();
-
- /// <summary>
- /// Gets a value indicating if the last operation left the <see cref="Blob"/> in an undefined state
- /// </summary>
- /// <returns>True if the <see cref="Blob"/> state is undefined, false otherwise</returns>
- public static bool IsError(this Blob blob) => bool.TrueString.Equals(blob[ERROR_FLAG]);
- internal static void IsError(this LWStorageDescriptor blob, bool value) => blob[ERROR_FLAG] = value ? bool.TrueString : null;
-
- internal static long GetLength(this LWStorageDescriptor blob) => (blob as IObjectStorage).GetObject<long>(FILE_SIZE);
- internal static void SetLength(this LWStorageDescriptor blob, long length) => (blob as IObjectStorage).SetObject(FILE_SIZE, length);
-
- internal static string GetName(this LWStorageDescriptor blob) => blob[FILE_NAME];
- internal static string SetName(this LWStorageDescriptor blob, string filename) => blob[FILE_NAME] = filename;
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobStore.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobStore.cs
deleted file mode 100644
index e59997c..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/BlobStore.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: BlobStore.cs
-*
-* BlobStore.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.IO;
-using System.Security.Cryptography;
-using System.Threading.Tasks;
-
-using VNLib.Utils.Extensions;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
-
- /// <summary>
- /// Stores <see cref="Blob"/>s to the local file system backed with a single table <see cref="LWStorageManager"/>
- /// that tracks changes
- /// </summary>
- public class BlobStore
- {
- /// <summary>
- /// The root directory all blob files are stored
- /// </summary>
- public DirectoryInfo RootDir { get; }
- /// <summary>
- /// The backing store for blob meta-data
- /// </summary>
- protected LWStorageManager BlobTable { get; }
- /// <summary>
- /// Creates a new <see cref="BlobStore"/> that accesses files
- /// within the specified root directory.
- /// </summary>
- /// <param name="rootDir">The root directory containing the blob file contents</param>
- /// <param name="blobStoreMan">The db backing store</param>
- public BlobStore(DirectoryInfo rootDir, LWStorageManager blobStoreMan)
- {
- RootDir = rootDir;
- BlobTable = blobStoreMan;
- }
-
- private string GetPath(string fileId) => Path.Combine(RootDir.FullName, fileId);
-
- /*
- * Creates a repeatable unique identifier for the file
- * name and allows for lookups
- */
- internal static string CreateFileHash(string fileName)
- {
- throw new NotImplementedException();
- //return ManagedHash.ComputeBase64Hash(fileName, HashAlg.SHA1);
- }
-
- /// <summary>
- /// Opens an existing <see cref="Blob"/> from the current store
- /// </summary>
- /// <param name="fileId">The id of the file being requested</param>
- /// <param name="access">Access level of the file</param>
- /// <param name="share">The sharing option of the underlying file</param>
- /// <param name="bufferSize">The size of the file buffer</param>
- /// <returns>If found, the requested <see cref="Blob"/>, null otherwise. Throws exceptions if the file is opened in a non-sharable state</returns>
- /// <exception cref="IOException"></exception>
- /// <exception cref="NotSupportedException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="UnauthorizedAccessException"></exception>
- /// <exception cref="UndefinedBlobStateException"></exception>
- public virtual async Task<Blob> OpenBlobAsync(string fileId, FileAccess access, FileShare share, int bufferSize = 4096)
- {
- //Get the file's data descriptor
- LWStorageDescriptor fileDescriptor = await BlobTable.GetDescriptorFromIDAsync(fileId);
- //return null if not found
- if (fileDescriptor == null)
- {
- return null;
- }
- try
- {
- string fsSafeName = GetPath(fileDescriptor.DescriptorID);
- //try to open the file
- FileStream file = new(fsSafeName, FileMode.Open, access, share, bufferSize, FileOptions.Asynchronous);
- //Create the new blob
- return new Blob(fileDescriptor, file);
- }
- catch (FileNotFoundException)
- {
- //If the file was not found but the descriptor was, delete the descriptor from the db
- fileDescriptor.Delete();
- //Flush changes
- await fileDescriptor.ReleaseAsync();
- //return null since this is a desync issue and the file technically does not exist
- return null;
- }
- catch
- {
- //Release the descriptor and pass the exception
- await fileDescriptor.ReleaseAsync();
- throw;
- }
- }
-
- /// <summary>
- /// Creates a new <see cref="Blob"/> for the specified file sharing permissions
- /// </summary>
- /// <param name="name">The name of the original file</param>
- /// <param name="share">The blob sharing permissions</param>
- /// <param name="bufferSize"></param>
- /// <returns>The newly created <see cref="Blob"/></returns>
- /// <exception cref="IoExtensions"></exception>
- /// <exception cref="NotSupportedException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="UnauthorizedAccessException"></exception>
- public virtual async Task<Blob> CreateBlobAsync(string name, FileShare share = FileShare.None, int bufferSize = 4096)
- {
- //hash the file name to create a unique id for the file name
- LWStorageDescriptor newFile = await BlobTable.CreateDescriptorAsync(CreateFileHash(name));
- //if the descriptor was not created, return null
- if (newFile == null)
- {
- return null;
- }
- try
- {
- string fsSafeName = GetPath(newFile.DescriptorID);
- //Open/create the new file
- FileStream file = new(fsSafeName, FileMode.OpenOrCreate, FileAccess.ReadWrite, share, bufferSize, FileOptions.Asynchronous);
- //If the file already exists, make sure its zero'd
- file.SetLength(0);
- //Save the original name of the file
- newFile.SetName(name);
- //Create and return the new blob
- return new Blob(newFile, file);
- }
- catch
- {
- //If an exception occurs, remove the descritor from the db
- newFile.Delete();
- await newFile.ReleaseAsync();
- //Pass exception
- throw;
- }
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWDecriptorCreationException.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWDecriptorCreationException.cs
deleted file mode 100644
index 7ea50d0..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWDecriptorCreationException.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWDecriptorCreationException.cs
-*
-* LWDecriptorCreationException.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data.Storage
-{
- /// <summary>
- /// Raised when an operation to create a new <see cref="LWStorageDescriptor"/>
- /// fails
- /// </summary>
- public class LWDescriptorCreationException : Exception
- {
- ///<inheritdoc/>
- public LWDescriptorCreationException()
- {}
- ///<inheritdoc/>
- public LWDescriptorCreationException(string? message) : base(message)
- {}
- ///<inheritdoc/>
- public LWDescriptorCreationException(string? message, Exception? innerException) : base(message, innerException)
- {}
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageRemoveFailedException.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageRemoveFailedException.cs
deleted file mode 100644
index d91019c..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageRemoveFailedException.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWStorageRemoveFailedException.cs
-*
-* LWStorageRemoveFailedException.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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 VNLib.Utils.Resources;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
- /// <summary>
- /// The exception raised when an open <see cref="LWStorageDescriptor"/> removal operation fails. The
- /// <see cref="Exception.InnerException"/> property may contain any nested exceptions that caused the removal to fail.
- /// </summary>
- public class LWStorageRemoveFailedException : ResourceDeleteFailedException
- {
- internal LWStorageRemoveFailedException(string error, Exception inner) : base(error, inner) { }
-
- public LWStorageRemoveFailedException()
- {}
-
- public LWStorageRemoveFailedException(string message) : base(message)
- {}
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageUpdateFailedException.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageUpdateFailedException.cs
deleted file mode 100644
index f13792d..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/LWStorageUpdateFailedException.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWStorageUpdateFailedException.cs
-*
-* LWStorageUpdateFailedException.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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 VNLib.Utils.Resources;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
- /// <summary>
- /// The exception raised when an open <see cref="LWStorageDescriptor"/> update operation fails. The
- /// <see cref="Exception.InnerException"/> property may contain any nested exceptions that caused the update to fail.
- /// </summary>
- public class LWStorageUpdateFailedException : ResourceUpdateFailedException
- {
- internal LWStorageUpdateFailedException(string error, Exception inner) : base(error, inner) { }
-
- public LWStorageUpdateFailedException()
- {}
- public LWStorageUpdateFailedException(string message) : base(message)
- {}
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/UndefinedBlobStateException.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/UndefinedBlobStateException.cs
deleted file mode 100644
index c52fed9..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/Exceptions/UndefinedBlobStateException.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-* Copyright (c) 2024 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: UndefinedBlobStateException.cs
-*
-* UndefinedBlobStateException.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data.Storage
-{
- /// <summary>
- /// Raised to signal that the requested <see cref="Blob"/> was left in an undefined state
- /// when previously accessed
- /// </summary>
- public class UndefinedBlobStateException : Exception
- {
- public UndefinedBlobStateException()
- {}
- public UndefinedBlobStateException(string message) : base(message)
- {}
- public UndefinedBlobStateException(string message, Exception innerException) : base(message, innerException)
- {}
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
deleted file mode 100644
index 032d380..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWStorageContext.cs
-*
-* LWStorageContext.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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 Microsoft.EntityFrameworkCore;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
-#nullable disable
- internal sealed class LWStorageContext : DBContextBase
- {
- private readonly string TableName;
- public DbSet<LWStorageEntry> Descriptors { get; set; }
-
- public LWStorageContext(DbContextOptions options, string tableName)
- :base(options)
- {
- TableName = tableName;
- }
-
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- 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
deleted file mode 100644
index 78fdfa0..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageDescriptor.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWStorageDescriptor.cs
-*
-* LWStorageDescriptor.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Text.Json;
-using System.Threading;
-using System.Collections;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-using VNLib.Utils;
-using VNLib.Utils.Async;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
- /// <summary>
- /// Represents an open storage object, that when released or disposed, will flush its changes to the underlying table
- /// for which this descriptor represents
- /// </summary>
- public sealed class LWStorageDescriptor : AsyncUpdatableResource, IObjectStorage, IEnumerable<KeyValuePair<string, string>>, IIndexable<string, string>
- {
-
- private static readonly JsonSerializerOptions SerializerOptions = new()
- {
- DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
- NumberHandling = JsonNumberHandling.Strict,
- ReadCommentHandling = JsonCommentHandling.Disallow,
- WriteIndented = false,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- IgnoreReadOnlyFields = true,
- DefaultBufferSize = Environment.SystemPageSize,
- };
-
-
- internal LWStorageEntry Entry { get; }
-
- private readonly Lazy<Dictionary<string, string>> StringStorage;
-
- protected override IAsyncResourceStateHandler AsyncHandler { get; }
-
- /// <summary>
- /// The currnt descriptor's identifier string within its backing table. Usually the primary key.
- /// </summary>
- public string DescriptorID => Entry.Id;
-
- /// <summary>
- /// The identifier of the user for which this descriptor belongs to
- /// </summary>
- public string UserID => Entry.UserId!;
-
- /// <summary>
- /// The <see cref="DateTime"/> when the descriptor was created
- /// </summary>
- public DateTimeOffset Created => Entry.Created;
-
- /// <summary>
- /// The last time this descriptor was updated
- /// </summary>
- public DateTimeOffset LastModified => Entry.LastModified;
-
- internal LWStorageDescriptor(IAsyncResourceStateHandler handler, LWStorageEntry entry)
- {
- Entry = entry;
- AsyncHandler = handler;
- StringStorage = new(OnStringStoreLoad);
- }
-
- internal Dictionary<string, string> OnStringStoreLoad()
- {
- if(Entry.Data == null || Entry.Data.Length == 0)
- {
- return new(StringComparer.OrdinalIgnoreCase);
- }
- else
- {
- //Decode and deserialize the data
- return JsonSerializer.Deserialize<Dictionary<string, string>>(Entry.Data, SerializerOptions) ?? new(StringComparer.OrdinalIgnoreCase);
- }
- }
-
- /// <inheritdoc/>
- /// <exception cref="JsonException"></exception>
- /// <exception cref="NotSupportedException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public T? GetObject<T>(string key)
- {
- Check();
- //De-serialize and return object
- return StringStorage.Value.TryGetValue(key, out string? val) ? JsonSerializer.Deserialize<T>(val, SerializerOptions) : default;
- }
-
- /// <inheritdoc/>
- /// <exception cref="NotSupportedException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public void SetObject<T>(string key, T obj)
- {
- //Remove the object from storage if its null
- if (obj == null)
- {
- SetStringValue(key, null);
- }
- else
- {
- //Serialize the object to a string
- string value = JsonSerializer.Serialize(obj, SerializerOptions);
- //Attempt to store string in storage
- SetStringValue(key, value);
- }
- }
-
- /// <summary>
- /// Gets a string value from string storage matching a given key
- /// </summary>
- /// <param name="key">Key for storage</param>
- /// <returns>Value associaetd with key if exists, <see cref="string.Empty"/> otherwise</returns>
- /// <exception cref="ArgumentNullException">If key is null</exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public string GetStringValue(string key)
- {
- Check();
- return StringStorage.Value.TryGetValue(key, out string? val) ? val : string.Empty;
- }
-
- /// <summary>
- /// Creates, overwrites, or removes a string value identified by key.
- /// </summary>
- /// <param name="key">Entry key</param>
- /// <param name="value">String to store or overwrite, set to null or string.Empty to remove a property</param>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <exception cref="ArgumentNullException">If key is null</exception>
- public void SetStringValue(string key, string? value)
- {
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentNullException(nameof(key));
- }
- Check();
- //If the value is null, see if the the properties are null
- if (string.IsNullOrWhiteSpace(value))
- {
- //If the value is null and properies exist, remove the entry
- StringStorage.Value.Remove(key);
- Modified |= true;
- }
- else
- {
- //Set the value
- StringStorage.Value[key] = value;
- //Set modified flag
- Modified |= true;
- }
- }
-
- /// <summary>
- /// Gets or sets a string value from string storage matching a given key
- /// </summary>
- /// <param name="key">Key for storage</param>
- /// <returns>Value associaetd with key if exists, <seealso cref="string.Empty "/> otherwise</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <exception cref="ArgumentNullException">If key is null</exception>
- public string this[string key]
- {
- get => GetStringValue(key);
- set => SetStringValue(key, value);
- }
-
- /// <summary>
- /// Flushes all pending changes to the backing store asynchronously
- /// </summary>
- /// <exception cref="ObjectDisposedException"></exception>
- public ValueTask WritePendingChangesAsync()
- {
- Check();
- return Modified ? (new(FlushPendingChangesAsync())) : ValueTask.CompletedTask;
- }
-
- ///<inheritdoc/>
- public override async ValueTask ReleaseAsync(CancellationToken cancellation = default)
- {
- await base.ReleaseAsync(cancellation);
-
- //Cleanup dict on exit
- if (StringStorage.IsValueCreated)
- {
- StringStorage.Value.Clear();
- }
- }
-
- ///<inheritdoc/>
- public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => StringStorage.Value.GetEnumerator();
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- ///<inheritdoc/>
- protected override object GetResource()
- {
- //Serlaize the state data and store it in the data entry
- Entry.Data = JsonSerializer.SerializeToUtf8Bytes(StringStorage.Value, SerializerOptions);
- return Entry;
- }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageEntry.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageEntry.cs
deleted file mode 100644
index 07622e5..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageEntry.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWStorageEntry.cs
-*
-* LWStorageEntry.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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 VNLib.Plugins.Extensions.Data.Abstractions;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
-
- internal sealed class LWStorageEntry : DbModelBase, IUserEntity
- {
- public override string Id { get; set; }
-
- public override DateTime Created { get; set; }
-
- public override DateTime LastModified { get; set; }
-
- public string? UserId { get; set; }
-
- public byte[]? Data { get; set; }
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
deleted file mode 100644
index f098def..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
-* Copyright (c) 2024 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: LWStorageManager.cs
-*
-* LWStorageManager.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extensions.Data 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.Data 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.Data;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Microsoft.EntityFrameworkCore;
-
-using VNLib.Utils;
-using VNLib.Utils.Async;
-
-namespace VNLib.Plugins.Extensions.Data.Storage
-{
-
- /// <summary>
- /// Provides single table database object storage services
- /// </summary>
- public sealed class LWStorageManager : IAsyncResourceStateHandler
- {
-
- /// <summary>
- /// The generator function that is invoked when a new <see cref="LWStorageDescriptor"/> is to
- /// be created without an explicit id
- /// </summary>
- public Func<string> NewDescriptorIdGenerator { get; init; } = static () => Guid.NewGuid().ToString("N");
-
- private readonly DbContextOptions DbOptions;
- private readonly string TableName;
-
- private LWStorageContext GetContext() => new(DbOptions, TableName);
-
- /// <summary>
- /// Creates a new <see cref="LWStorageManager"/> with
- /// </summary>
- /// <param name="options">The db context options to create database connections with</param>
- /// <param name="tableName">The name of the table to operate on</param>
- /// <exception cref="ArgumentNullException"></exception>
- public LWStorageManager(DbContextOptions options, string tableName)
- {
- DbOptions = options ?? throw new ArgumentNullException(nameof(options));
- TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
- }
-
- /// <summary>
- /// Creates a new <see cref="LWStorageDescriptor"/> fror a given user
- /// </summary>
- /// <param name="userId">Id of user</param>
- /// <param name="descriptorIdOverride">An override to specify the new descriptor's id</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>A new <see cref="LWStorageDescriptor"/> if successfully created, null otherwise</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="LWDescriptorCreationException"></exception>
- public async Task<LWStorageDescriptor> CreateDescriptorAsync(string userId, string? descriptorIdOverride = null, CancellationToken cancellation = default)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(userId);
-
- //If no override id was specified, generate a new one
- descriptorIdOverride ??= NewDescriptorIdGenerator();
-
- DateTime createdOrModifedTime = DateTime.UtcNow;
-
- await using LWStorageContext ctx = GetContext();
-
- //Make sure the descriptor doesnt exist only by its descriptor id
- if (await ctx.Descriptors.AnyAsync(d => d.Id == descriptorIdOverride, cancellation))
- {
- throw new LWDescriptorCreationException($"A descriptor with id {descriptorIdOverride} already exists");
- }
-
- //Cache time
- DateTime now = DateTime.UtcNow;
-
- //Create the new descriptor
- LWStorageEntry entry = new()
- {
- Created = now,
- LastModified = now,
- Id = descriptorIdOverride,
- UserId = userId,
- };
-
- //Add and save changes
- ctx.Descriptors.Add(entry);
-
- ERRNO result = await ctx.SaveAndCloseAsync(true, cancellation);
-
- return result
- ? new LWStorageDescriptor(this, entry)
- : throw new LWDescriptorCreationException("Failed to create descriptor, because changes could not be saved");
- }
-
- /// <summary>
- /// Attempts to retrieve <see cref="LWStorageDescriptor"/> for a given user-id. The caller is responsible for
- /// consitancy state of the descriptor
- /// </summary>
- /// <param name="userid">User's id</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>The descriptor belonging to the user, or null if not found or error occurs</returns>
- /// <exception cref="ArgumentNullException"></exception>
- public async Task<LWStorageDescriptor?> GetDescriptorFromUIDAsync(string userid, CancellationToken cancellation = default)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(userid);
-
- //Init db
- await using LWStorageContext db = GetContext();
-
- //Get entry
- LWStorageEntry? entry = await (from s in db.Descriptors
- where s.UserId == userid
- select s)
- .SingleOrDefaultAsync(cancellation);
-
- await db.SaveAndCloseAsync(true, cancellation);
-
- //Close transactions and return
- return entry == null ? null : new (this, entry);
- }
-
- /// <summary>
- /// Attempts to retrieve the <see cref="LWStorageDescriptor"/> for the given descriptor id. The caller is responsible for
- /// consitancy state of the descriptor
- /// </summary>
- /// <param name="descriptorId">Unique identifier for the descriptor</param>
- /// <param name="cancellation">A token to cancel the opreeaiton</param>
- /// <returns>The descriptor belonging to the user, or null if not found or error occurs</returns>
- /// <exception cref="ArgumentNullException"></exception>
- public async Task<LWStorageDescriptor?> GetDescriptorFromIDAsync(string descriptorId, CancellationToken cancellation = default)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(descriptorId);
-
- //Init db
- await using LWStorageContext db = GetContext();
-
- //Get entry
- LWStorageEntry? entry = await (from s in db.Descriptors
- where s.Id == descriptorId
- select s)
- .SingleOrDefaultAsync(cancellation);
-
- await db.SaveAndCloseAsync(true, cancellation);
-
- //Close transactions and return
- return entry == null ? null : new(this, entry);
- }
-
- /// <summary>
- /// Cleanup entries before the specified <see cref="TimeSpan"/>. Entires are store in UTC time
- /// </summary>
- /// <param name="compareTime">Time before <see cref="DateTime.UtcNow"/> to compare against</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>The number of entires cleaned</returns>S
- public Task<ERRNO> CleanupTableAsync(TimeSpan compareTime, CancellationToken cancellation = default) => CleanupTableAsync(DateTime.UtcNow.Subtract(compareTime), cancellation);
-
- /// <summary>
- /// Cleanup entries before the specified <see cref="DateTime"/>. Entires are store in UTC time
- /// </summary>
- /// <param name="compareTime">UTC time to compare entires against</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>The number of entires cleaned</returns>
- public async Task<ERRNO> CleanupTableAsync(DateTime compareTime, CancellationToken cancellation = default)
- {
- //Init db
- await using LWStorageContext db = GetContext();
-
- //Get all expired entires
- LWStorageEntry[] expired = await (from s in db.Descriptors
- where s.Created < compareTime
- select s)
- .ToArrayAsync(cancellation);
-
- //Delete
- db.Descriptors.RemoveRange(expired);
-
- //Commit transaction
- return await db.SaveAndCloseAsync(true, cancellation);
- }
-
- async Task IAsyncResourceStateHandler.UpdateAsync(AsyncUpdatableResource resource, object state, CancellationToken cancellation)
- {
- LWStorageEntry entry = (state as LWStorageEntry)!;
- ERRNO result = 0;
- try
- {
- await using LWStorageContext ctx = GetContext();
-
- //Begin tracking
- ctx.Descriptors.Attach(entry);
-
- //Update modified time
- entry.LastModified = DateTime.UtcNow;
-
- //Save changes
- result = await ctx.SaveAndCloseAsync(true, cancellation);
- }
- catch (Exception ex)
- {
- throw new LWStorageUpdateFailedException("", ex);
- }
- //If the result is 0 then the update failed
- if (!result)
- {
- throw new LWStorageUpdateFailedException($"Descriptor {entry.Id} failed to update");
- }
- }
-
- async Task IAsyncResourceStateHandler.DeleteAsync(AsyncUpdatableResource resource, CancellationToken cancellation)
- {
- LWStorageEntry descriptor = (resource as LWStorageDescriptor)!.Entry;
- ERRNO result;
- try
- {
- //Init db
- await using LWStorageContext db = GetContext();
-
- //Delete the user from the database
- db.Descriptors.Remove(descriptor);
-
- //Save changes and commit if successful
- result = await db.SaveAndCloseAsync(true, cancellation);
- }
- catch (Exception ex)
- {
- throw new LWStorageRemoveFailedException("", ex);
- }
- if (!result)
- {
- throw new LWStorageRemoveFailedException("Failed to delete the user account because of a database failure, the user may already be deleted");
- }
- }
- }
-} \ No newline at end of file
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 c957b26..94e7137 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
@@ -48,7 +48,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
</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 5292a5d..d622a05 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
@@ -40,7 +40,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs
index 7f5c09c..d8f4347 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/ConfigScope.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -29,13 +29,15 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using VNLib.Utils.Resources;
+
namespace VNLib.Plugins.Extensions.Loading
{
internal sealed class ConfigScope: IConfigScope
{
- private readonly Lazy<IReadOnlyDictionary<string, JsonElement>> _config;
+ private readonly LazyInitializer<IReadOnlyDictionary<string, JsonElement>> _config;
private readonly JsonElement _element;
@@ -48,36 +50,40 @@ namespace VNLib.Plugins.Extensions.Loading
private IReadOnlyDictionary<string, JsonElement> LoadTable()
{
- return _element.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
+ return _element.EnumerateObject()
+ .ToDictionary(
+ static k => k.Name,
+ static k => k.Value
+ );
}
///<inheritdoc/>
- public JsonElement this[string key] => _config.Value[key];
+ public JsonElement this[string key] => _config.Instance[key];
///<inheritdoc/>
- public IEnumerable<string> Keys => _config.Value.Keys;
+ public IEnumerable<string> Keys => _config.Instance.Keys;
///<inheritdoc/>
- public IEnumerable<JsonElement> Values => _config.Value.Values;
+ public IEnumerable<JsonElement> Values => _config.Instance.Values;
///<inheritdoc/>
- public int Count => _config.Value.Count;
+ public int Count => _config.Instance.Count;
///<inheritdoc/>
public string ScopeName { get; }
///<inheritdoc/>
- public bool ContainsKey(string key) => _config.Value.ContainsKey(key);
+ public bool ContainsKey(string key) => _config.Instance.ContainsKey(key);
///<inheritdoc/>
public T Deserialze<T>() => _element.Deserialize<T>()!;
///<inheritdoc/>
- public IEnumerator<KeyValuePair<string, JsonElement>> GetEnumerator() => _config.Value.GetEnumerator();
+ public IEnumerator<KeyValuePair<string, JsonElement>> GetEnumerator() => _config.Instance.GetEnumerator();
///<inheritdoc/>
- public bool TryGetValue(string key, [MaybeNullWhen(false)] out JsonElement value) => _config.Value.TryGetValue(key, out value);
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out JsonElement value) => _config.Instance.TryGetValue(key, out value);
- IEnumerator IEnumerable.GetEnumerator() => _config.Value.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => _config.Instance.GetEnumerator();
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs
index 6d4641b..6eeba78 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/IOnConfigValidation.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -33,6 +33,6 @@ namespace VNLib.Plugins.Extensions.Loading
/// <summary>
/// Validates a json configuration during deserialzation
/// </summary>
- void Validate();
+ void OnValidate();
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/Validate.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/Validate.cs
new file mode 100644
index 0000000..1bb6787
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Configuration/Validate.cs
@@ -0,0 +1,114 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: Validate.cs
+*
+* Validate.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.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.IO;
+
+namespace VNLib.Plugins.Extensions.Loading.Configuration
+{
+ /// <summary>
+ /// A class that allows for easy configuration validation
+ /// </summary>
+ public sealed class Validate
+ {
+ /// <summary>
+ /// Ensures the object is not null and not an empty string,
+ /// otherwise a <see cref="ConfigurationValidationException"/> is raised
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="obj">The object to test</param>
+ /// <param name="message">The message to display to the user on loading</param>
+ /// <exception cref="ConfigurationValidationException"></exception>
+ [DoesNotReturn]
+ public static void NotNull<T>(T? obj, string message) where T : class
+ {
+ if (obj is null)
+ {
+ throw new ConfigurationValidationException(message);
+ }
+
+ if (obj is string s && string.IsNullOrWhiteSpace(s))
+ {
+ throw new ConfigurationValidationException(message);
+ }
+ }
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="condition"></param>
+ /// <param name="message"></param>
+ /// <exception cref="ConfigurationValidationException"></exception>
+ public static void Assert([DoesNotReturnIf(false)] bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new ConfigurationValidationException(message);
+ }
+ }
+
+ public static void NotEqual<T>(T a, T b, string message)
+ {
+ if (a is null || b is null)
+ {
+ throw new ConfigurationValidationException(message);
+ }
+
+ if (a.Equals(b))
+ {
+ throw new ConfigurationValidationException(message);
+ }
+ }
+
+ public static void Range2<T>(T value, T min, T max, string message)
+ where T : IComparable<T>
+ {
+ //Compare the value against min/max calues and raise exception if it is
+ if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
+ {
+ throw new ConfigurationValidationException(message);
+ }
+ }
+
+
+ public static void Range<T>(T value, T min, T max, [CallerArgumentExpression(nameof(value))] string? paramName = null)
+ where T : IComparable<T>
+ {
+
+ Range2(value, min, max, $"Value for {paramName} must be between {min} and {max}. Value: {value}");
+ }
+
+
+ public static void FileExists(string path)
+ {
+ if (!FileOperations.FileExists(path))
+ {
+ throw new ConfigurationValidationException($"Required file: {path} not found");
+ }
+ }
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
index 0337fbd..e838822 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
@@ -24,12 +24,14 @@
using System;
using System.IO;
+using System.Linq;
using System.Text.Json;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using VNLib.Utils.Extensions;
+using VNLib.Plugins.Extensions.Loading.Configuration;
namespace VNLib.Plugins.Extensions.Loading
{
@@ -37,22 +39,17 @@ namespace VNLib.Plugins.Extensions.Loading
/// Specifies a configuration variable name in the plugin's configuration
/// containing data specific to the type
/// </summary>
+ /// <remarks>
+ /// Initializes a new <see cref="ConfigurationNameAttribute"/>
+ /// </remarks>
+ /// <param name="configVarName">The name of the configuration variable for the class</param>
[AttributeUsage(AttributeTargets.Class)]
- public sealed class ConfigurationNameAttribute : Attribute
+ public sealed class ConfigurationNameAttribute(string configVarName) : Attribute
{
/// <summary>
///
/// </summary>
- public string ConfigVarName { get; }
-
- /// <summary>
- /// Initializes a new <see cref="ConfigurationNameAttribute"/>
- /// </summary>
- /// <param name="configVarName">The name of the configuration variable for the class</param>
- public ConfigurationNameAttribute(string configVarName)
- {
- ConfigVarName = configVarName;
- }
+ public string ConfigVarName { get; } = configVarName;
/// <summary>
/// When true or not configured, signals that the type requires a configuration scope
@@ -71,7 +68,6 @@ namespace VNLib.Plugins.Extensions.Loading
public const string S3_SECRET_KEY = "s3_secret";
public const string PLUGIN_ASSET_KEY = "assets";
public const string PLUGINS_HOST_KEY = "plugins";
- public const string PLUGIN_PATH_KEY = "path";
/// <summary>
/// Retrieves a top level configuration dictionary of elements for the specified type.
@@ -80,7 +76,7 @@ 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="ConfigurationException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
public static IConfigScope GetConfigForType<T>(this PluginBase plugin)
{
@@ -97,26 +93,12 @@ namespace VNLib.Plugins.Extensions.Loading
/// <param name="plugin"></param>
/// <param name="propName">The config property name to retrieve</param>
/// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
- /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ConfigurationException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
public static IConfigScope GetConfig(this PluginBase plugin, string propName)
- {
- plugin.ThrowIfUnloaded();
- try
- {
- //Try to get the element from the plugin config first
- if (!plugin.PluginConfig.TryGetProperty(propName, out JsonElement el))
- {
- //Fallback to the host config
- el = plugin.HostConfig.GetProperty(propName);
- }
- //Get the top level config as a dictionary
- return new ConfigScope(el, propName);
- }
- catch (KeyNotFoundException)
- {
- throw new KeyNotFoundException($"Missing required top level configuration object '{propName}', in host/plugin configuration files");
- }
+ {
+ return TryGetConfig(plugin, propName)
+ ?? throw new ConfigurationException($"Missing required top level configuration object '{propName}', in host/plugin configuration files");
}
/// <summary>
@@ -154,7 +136,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <exception cref="ObjectDisposedException"></exception>
public static IConfigScope GetConfigForType(this PluginBase plugin, Type type)
{
- _ = type ?? throw new ArgumentNullException(nameof(type));
+ ArgumentNullException.ThrowIfNull(type);
string? configName = GetConfigNameForType(type);
@@ -178,11 +160,12 @@ namespace VNLib.Plugins.Extensions.Loading
public static T? GetProperty<T>(this IConfigScope config, string property, Func<JsonElement, T> getter)
{
//Check null
- _ = config ?? throw new ArgumentNullException(nameof(config));
- _ = property ?? throw new ArgumentNullException(nameof(property));
- _ = getter ?? throw new ArgumentNullException(nameof(getter));
-
- return !config.TryGetValue(property, out JsonElement el) ? default : getter(el);
+ ArgumentNullException.ThrowIfNull(config);
+ ArgumentNullException.ThrowIfNull(getter);
+ ArgumentException.ThrowIfNullOrWhiteSpace(property);
+ return !config.TryGetValue(property, out JsonElement el)
+ ? default
+ : getter(el);
}
/// <summary>
@@ -194,7 +177,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <param name="getter">A function to get the value from the json type</param>
/// <returns>The property value</returns>
/// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ConfigurationException"></exception>
public static T GetRequiredProperty<T>(this IConfigScope config, string property, Func<JsonElement, T> getter)
{
//Check null
@@ -203,13 +186,32 @@ namespace VNLib.Plugins.Extensions.Loading
ArgumentNullException.ThrowIfNull(getter);
//Get the property
- if (!config.TryGetValue(property, out JsonElement el))
- {
- throw new KeyNotFoundException($"Missing required configuration property '{property}' in config {config.ScopeName}");
- }
+ bool hasValue = config.TryGetValue(property, out JsonElement el);
+ Validate.Assert(hasValue, $"Missing required configuration property '{property}' in config {config.ScopeName}");
+
+ T? value = getter(el);
+ Validate.Assert(value is not null, $"Missing required configuration property '{property}' in config {config.ScopeName}");
- //Even if the getter returns null, throw
- return getter(el) ?? throw new KeyNotFoundException($"Missing required configuration property '{property}' in config {config.ScopeName}");
+ //Attempt to validate if the configuration inherits the interface
+ TryValidateConfig(value);
+
+ return value;
+ }
+
+
+ /// <summary>
+ /// Gets a required configuration property from the specified configuration scope
+ /// and deserializes the json type.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="config"></param>
+ /// <param name="property">The name of the property to get</param>
+ /// <returns>The property value deserialzied into the desired object</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ConfigurationException"></exception>
+ public static T GetRequiredProperty<T>(this IConfigScope config, string property)
+ {
+ return GetRequiredProperty(config, property, static p => p.Deserialize<T>()!);
}
/// <summary>
@@ -261,6 +263,28 @@ namespace VNLib.Plugins.Extensions.Loading
}
/// <summary>
+ /// Gets a configuration property from the specified configuration scope
+ /// and invokes your callback function on the element if found to transform the
+ /// output value, or returns the default value if the property is not found.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="config"></param>
+ /// <param name="property">The name of the configuration element to get</param>
+ /// <param name="defaultValue">The default value to return</param>
+ /// <returns>The property value returned from your getter callback, or the default value if not found</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ [return: NotNullIfNotNull(nameof(defaultValue))]
+ public static T? GetValueOrDefault<T>(this IConfigScope config, string property, T defaultValue)
+ {
+ return GetValueOrDefault(
+ config,
+ property,
+ static p => p.Deserialize<T>(),
+ defaultValue
+ );
+ }
+
+ /// <summary>
/// Gets the configuration property name for the type
/// </summary>
/// <param name="type">The type to get the configuration name for</param>
@@ -289,7 +313,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// for missing configuration for a given type
/// </summary>
/// <param name="type">The type to raise exception for</param>
- /// <exception cref="KeyNotFoundException"></exception>
+ /// <exception cref="ConfigurationException"></exception>
[DoesNotReturn]
public static void ThrowConfigNotFoundForType(Type type)
{
@@ -297,11 +321,11 @@ namespace VNLib.Plugins.Extensions.Loading
string? configName = GetConfigNameForType(type);
if (configName != null)
{
- throw new KeyNotFoundException($"Missing required configuration key '{configName}' for type {type.Name}");
+ throw new ConfigurationException($"Missing required configuration key '{configName}' for type {type.Name}");
}
else
{
- throw new KeyNotFoundException($"Missing required configuration key for type {type.Name}");
+ throw new ConfigurationException($"Missing required configuration key for type {type.Name}");
}
}
@@ -321,7 +345,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <summary>
/// Deserialzes the configuration to the desired object and calls its
- /// <see cref="IOnConfigValidation.Validate"/> method. Validation exceptions
+ /// <see cref="IOnConfigValidation.OnValidate"/> method. Validation exceptions
/// are wrapped in a <see cref="ConfigurationValidationException"/>
/// </summary>
/// <typeparam name="T"></typeparam>
@@ -331,14 +355,9 @@ namespace VNLib.Plugins.Extensions.Loading
public static T DeserialzeAndValidate<T>(this IConfigScope scope) where T : IOnConfigValidation
{
T conf = scope.Deserialze<T>();
- try
- {
- conf.Validate();
- }
- catch(Exception ex)
- {
- throw new ConfigurationValidationException($"Configuration validation failed for type {typeof(T).Name}", ex);
- }
+
+ TryValidateConfig(conf);
+
return conf;
}
@@ -351,7 +370,6 @@ namespace VNLib.Plugins.Extensions.Loading
/// <returns>True if the plugin config contains the require configuration property</returns>
public static bool HasConfigForType<T>(this PluginBase plugin) => HasConfigForType(plugin, typeof(T));
-
/// <summary>
/// Determines if the current plugin configuration contains the require properties to initialize
/// the type
@@ -373,7 +391,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// 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"/>
+ /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.OnValidate"/>
/// method is invoked, and exceptions are warpped in <see cref="ConfigurationValidationException"/>
/// </para>
/// <para>
@@ -388,23 +406,13 @@ namespace VNLib.Plugins.Extensions.Loading
public static TConfig GetConfigElement<TConfig>(this PluginBase plugin)
{
//Deserialze the element
- TConfig config = plugin.GetConfigForType<TConfig>().Deserialze<TConfig>();
+ 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 ConfigurationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex);
- }
- }
+ TryValidateConfig(config);
//If async config, load async
- if(config is IAsyncConfigurable ac)
+ if (config is IAsyncConfigurable ac)
{
_ = plugin.ConfigureServiceAsync(ac);
}
@@ -416,7 +424,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// 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"/>
+ /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.OnValidate"/>
/// method is invoked, and exceptions are warpped in <see cref="ConfigurationValidationException"/>
/// </para>
/// <para>
@@ -432,31 +440,36 @@ namespace VNLib.Plugins.Extensions.Loading
public static TConfig GetConfigElement<TConfig>(this PluginBase plugin, string elementName)
{
//Deserialze the element
- TConfig config = plugin.GetConfig(elementName).Deserialze<TConfig>();
+ TConfig config = plugin.GetConfig(elementName)
+ .Deserialze<TConfig>();
+
+ TryValidateConfig(config);
+
+ //If async config, load async
+ if (config is IAsyncConfigurable ac)
+ {
+ _ = plugin.ConfigureServiceAsync(ac);
+ }
+ return config;
+ }
+
+ private static void TryValidateConfig<TConfig>(TConfig config)
+ {
//If the type is validatable, validate it
if (config is IOnConfigValidation conf)
{
try
{
- conf.Validate();
+ conf.OnValidate();
}
catch (Exception ex)
{
throw new ConfigurationValidationException($"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
@@ -492,16 +505,39 @@ namespace VNLib.Plugins.Extensions.Loading
/// </summary>
/// <param name="plugin"></param>
/// <returns>The absolute path to the directory containing all plugins</returns>
- public static string GetPluginsPath(this PluginBase plugin)
+ public static string[] GetPluginSearchDirs(this PluginBase plugin)
{
//Get global plugin config element
IConfigScope config = plugin.GetConfig(PLUGINS_HOST_KEY);
- //Get the plugins path or throw because it should ALWAYS be defined if this method is called
- string pluginsPath = config[PLUGIN_PATH_KEY].GetString()!;
+ /*
+ * Hosts are allowed to define mutliple plugin loading paths. A
+ * single path is supported for compat. Multi path takes precidence
+ * of course so attempt to load a string array first
+ */
+
+ if (!config.TryGetValue("paths", out JsonElement searchPaths)
+ && !config.TryGetValue("path", out searchPaths))
+ {
+ return [];
+ }
- //Get absolute path
- return Path.GetFullPath(pluginsPath);
+ if (searchPaths.ValueKind == JsonValueKind.Array)
+ {
+ //Get the plugins path or throw because it should ALWAYS be defined if this method is called
+ return searchPaths.EnumerateArray()
+ .Select(static p => p.GetString()!)
+ .Select(Path.GetFullPath) //Get absolute file paths
+ .ToArray();
+ }
+ else if (searchPaths.ValueKind == JsonValueKind.String)
+ {
+ return [Path.GetFullPath(searchPaths.GetString()!)];
+ }
+ else
+ {
+ return [];
+ }
}
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationException.cs
new file mode 100644
index 0000000..edfb002
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationException.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: ConfigurationException.cs
+*
+* ConfigurationException.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>
+ /// A base plugin configuration exception
+ /// </summary>
+ public class ConfigurationException : Exception
+ {
+ public ConfigurationException(string message) : base(message)
+ { }
+
+ public ConfigurationException(string message, Exception innerException) : base(message, innerException)
+ { }
+ public ConfigurationException()
+ { }
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs
index ebf4d9e..cedc41a 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -26,17 +26,18 @@ using System;
namespace VNLib.Plugins.Extensions.Loading
{
+
/// <summary>
/// An exception raised when a configuration validation exception has occured
/// </summary>
- public class ConfigurationValidationException : Exception
+ public class ConfigurationValidationException : ConfigurationException
{
public ConfigurationValidationException(string message) : base(message)
- {}
+ { }
public ConfigurationValidationException(string message, Exception innerException) : base(message, innerException)
- {}
+ { }
public ConfigurationValidationException()
- {}
+ { }
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
index b65c5e6..ca897e6 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
@@ -26,6 +26,7 @@ using System;
using System.IO;
using System.Linq;
using System.Text.Json;
+using System.Threading;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
@@ -104,22 +105,30 @@ namespace VNLib.Plugins.Extensions.Loading
plugin.ThrowIfUnloaded();
ArgumentNullException.ThrowIfNull(assemblyName);
+ string[] searchDirs;
+
/*
* Allow an assets directory to limit the scope of the search for the desired
* assembly, otherwise search all plugins directories
*/
string? assetDir = plugin.GetAssetsPath();
- assetDir ??= plugin.GetPluginsPath();
+
+ searchDirs = assetDir is null
+ ? plugin.GetPluginSearchDirs()
+ : ([assetDir]);
/*
- * 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
- */
- ArgumentNullException.ThrowIfNull(assetDir, "No plugin asset directory is defined for the current host configuration, this is likely a bug");
+ * 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
+ */
+ if (searchDirs.Length == 0)
+ {
+ throw new ConfigurationException("No plugin asset directory is defined for the current host configuration, this is likely a bug");
+ }
//Get the first file that matches the search file
- return Directory.EnumerateFiles(assetDir, assemblyName, searchOption).FirstOrDefault();
+ return searchDirs.SelectMany(d => Directory.EnumerateFiles(d, assemblyName, searchOption)).FirstOrDefault();
}
/// <summary>
@@ -260,9 +269,10 @@ namespace VNLib.Plugins.Extensions.Loading
/// </summary>
/// <param name="plugin"></param>
/// <exception cref="ObjectDisposedException"></exception>
- public static void ThrowIfUnloaded(this PluginBase plugin)
+ public static void ThrowIfUnloaded(this PluginBase? plugin)
{
//See if the plugin was unlaoded
+ ArgumentNullException.ThrowIfNull(plugin);
ObjectDisposedException.ThrowIf(plugin.UnloadToken.IsCancellationRequested, plugin);
}
@@ -294,6 +304,12 @@ namespace VNLib.Plugins.Extensions.Loading
//Optional delay
await Task.Delay(delayMs);
+ //If plugin unloads during delay, bail
+ if (plugin.UnloadToken.IsCancellationRequested)
+ {
+ return;
+ }
+
//Run on ts
Task deferred = Task.Run(asyncTask);
@@ -348,7 +364,7 @@ namespace VNLib.Plugins.Extensions.Loading
static async Task WaitForUnload(PluginBase pb, Action callback)
{
//Wait for unload as a task on the threadpool to avoid deadlocks
- await pb.UnloadToken.WaitHandle.WaitAsync()
+ await pb.UnloadToken.WaitHandle.NoSpinWaitAsync(Timeout.Infinite)
.ConfigureAwait(false);
callback();
@@ -649,9 +665,15 @@ namespace VNLib.Plugins.Extensions.Loading
}
catch(TargetInvocationException te) when (te.InnerException != null)
{
+ FindNestedConfigurationException(te);
FindAndThrowInnerException(te);
throw;
}
+ catch(Exception ex)
+ {
+ FindNestedConfigurationException(ex);
+ throw;
+ }
Task? loading = null;
@@ -747,6 +769,21 @@ namespace VNLib.Plugins.Extensions.Loading
}
}
+ internal static void FindNestedConfigurationException(Exception ex)
+ {
+ if(ex is ConfigurationException ce)
+ {
+ ExceptionDispatchInfo.Throw(ce);
+ }
+
+ //Recurse
+ if(ex.InnerException is not null)
+ {
+ FindNestedConfigurationException(ex.InnerException);
+ }
+
+ //No more exceptions
+ }
private sealed class PluginLocalCache
{
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointLogNameAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointLogNameAttribute.cs
new file mode 100644
index 0000000..d47be22
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointLogNameAttribute.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: ConfigurationExtensions.cs
+*
+* ConfigurationExtensions.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.Routing
+{
+
+ /// <summary>
+ /// Defines configurable settings for an endpoint
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class EndpointLogNameAttribute(string logName) : Attribute
+ {
+ /// <summary>
+ /// The name of the logging scope for the endpoint
+ /// </summary>
+ public string LogName { get; } = logName;
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointPathAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointPathAttribute.cs
new file mode 100644
index 0000000..a5ab355
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/EndpointPathAttribute.cs
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: ConfigurationExtensions.cs
+*
+* ConfigurationExtensions.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.Routing
+{
+ /// <summary>
+ /// Defines configurable settings for an endpoint
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class EndpointPathAttribute(string path) : Attribute
+ {
+ /// <summary>
+ /// Sets the endpoint path (or configuration template if set)
+ /// </summary>
+ public string Path { get; } = path;
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs
new file mode 100644
index 0000000..94771ab
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpControllerAttribute.cs
@@ -0,0 +1,36 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: HttpControllerAttribute.cs
+*
+* HttpControllerAttribute.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.Routing.Mvc
+{
+ /// <summary>
+ /// Attribute to define a controller for http routing. The class must be decorated
+ /// with this attribute to be recognized as a controller
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpControllerAttribute : Attribute
+ { }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs
new file mode 100644
index 0000000..d89df63
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/Mvc/HttpEndpointAttribute.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: HttpEndpointAttribute.cs
+*
+* HttpEndpointAttribute.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 VNLib.Net.Http;
+
+namespace VNLib.Plugins.Extensions.Loading.Routing.Mvc
+{
+
+ /// <summary>
+ /// Attribute to define an http endpoint for a controller. The class
+ /// must be decorated with the <see cref="HttpControllerAttribute"/> attribute
+ /// </summary>
+ /// <param name="path">The endpoint path</param>
+ /// <param name="method">The method (or methods) allowed to be filtered by this endpoint</param>
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpEndpointAttribute(string path, HttpMethod method) : Attribute
+ {
+ /// <summary>
+ /// The path of the endpoint
+ /// </summary>
+ public string Path { get; } = path;
+
+ /// <summary>
+ /// The http method of the endpoint. You may set more than one method
+ /// for a given endpoint
+ /// </summary>
+ public HttpMethod Method { get; } = method;
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs
index a1817a8..6665a75 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Routing/RoutingExtensions.cs
@@ -24,10 +24,19 @@
using System;
using System.Reflection;
+using System.Threading.Tasks;
+using System.Collections.Frozen;
using System.Collections.Generic;
+using System.Text.RegularExpressions;
using System.Runtime.CompilerServices;
+using VNLib.Net.Http;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Resources;
using VNLib.Plugins.Essentials.Runtime;
+using VNLib.Plugins.Essentials;
+using VNLib.Plugins.Essentials.Endpoints;
+using VNLib.Plugins.Extensions.Loading.Routing.Mvc;
namespace VNLib.Plugins.Extensions.Loading.Routing
{
@@ -35,7 +44,7 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
/// <summary>
/// Provides advanced QOL features to plugin loading
/// </summary>
- public static class RoutingExtensions
+ public static partial class RoutingExtensions
{
private static readonly ConditionalWeakTable<IEndpoint, PluginBase?> _pluginRefs = new();
private static readonly ConditionalWeakTable<PluginBase, EndpointCollection> _pluginEndpoints = new();
@@ -52,11 +61,14 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
T endpoint = plugin.CreateService<T>();
//Route the endpoint
- plugin.Route(endpoint);
+ Route(plugin, endpoint);
//Store ref to plugin for endpoint
_pluginRefs.Add(endpoint, plugin);
+ //Function that initalizes the endpoint's path and logging variables
+ InitEndpointSettings(plugin, endpoint);
+
return endpoint;
}
@@ -96,15 +108,119 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
{
_ = _pluginRefs.TryGetValue(ep, out PluginBase? pBase);
return pBase ?? throw new InvalidOperationException("Endpoint was not dynamically routed");
- }
-
+ }
+
+ private static readonly Regex ConfigSyntaxParser = ParserRegex();
+ private delegate void InitFunc(string path, ILogProvider log);
+
+ [GeneratedRegex("{{(.*?)}}", RegexOptions.Compiled)]
+ private static partial Regex ParserRegex();
+
+ private static void InitEndpointSettings<T>(PluginBase plugin, T endpoint) where T : IEndpoint
+ {
+ //Load optional config
+ IConfigScope config = plugin.GetConfigForType<T>();
+
+ ILogProvider logger = plugin.Log;
+
+ EndpointPathAttribute? pathAttr = typeof(T).GetCustomAttribute<EndpointPathAttribute>();
+
+ /*
+ * gets the protected function for assigning the endpoint path
+ * and logger instance.
+ */
+ InitFunc? initPathAndLog = ManagedLibrary.TryGetMethod<InitFunc>(endpoint, "InitPathAndLog", BindingFlags.NonPublic);
+
+ if (pathAttr is null || initPathAndLog is null)
+ {
+ return;
+ }
+
+ string? logName = typeof(T).GetCustomAttribute<EndpointLogNameAttribute>()?.LogName;
+
+ if (!string.IsNullOrWhiteSpace(logName))
+ {
+ logger = plugin.Log.CreateScope(SubsituteValue(logName, config));
+ }
+ try
+ {
+
+ //Invoke init function and pass in variable names
+ initPathAndLog(
+ path: SubsituteValue(pathAttr.Path, config),
+ logger
+ );
+ }
+ catch (ConfigurationException)
+ {
+ throw;
+ }
+ catch(Exception e)
+ {
+ throw new ConfigurationException($"Failed to initalize endpoint {endpoint.GetType().Name}", e);
+ }
+
+ static string SubsituteValue(string pathVar, IConfigScope? config)
+ {
+ if (config is null)
+ {
+ return pathVar;
+ }
+
+ // Replace the matched pattern with the corresponding value from the dictionary
+ return ConfigSyntaxParser.Replace(pathVar, match =>
+ {
+ string varName = match.Groups[1].Value;
+
+ //Get the value from the config scope or return the original variable unmodified
+ return config.GetValueOrDefault(varName, varName);
+ });
+ }
+ }
private sealed class EndpointCollection : IVirtualEndpointDefinition
{
public List<IEndpoint> Endpoints { get; } = new();
///<inheritdoc/>
- IEnumerable<IEndpoint> IVirtualEndpointDefinition.GetEndpoints() => Endpoints;
+ IEnumerable<IEndpoint> IVirtualEndpointDefinition.GetEndpoints() => Endpoints;
+ }
+
+
+ private delegate ValueTask<VfReturnType> EndpointWorkFunc(HttpEntity entity);
+
+ sealed record class HttpControllerEndpoint(MethodInfo MethodInfo, HttpEndpointAttribute Attr)
+ {
+ public string Path => Attr.Path;
+
+ public HttpMethod Method => Attr.Method;
+
+ public EndpointWorkFunc Func { get; } = MethodInfo.CreateDelegate<EndpointWorkFunc>();
+ }
+
+ private sealed class EndpointWrapper
+ : ResourceEndpointBase
+ {
+
+ private readonly FrozenDictionary<HttpMethod, EndpointWorkFunc> _wrappers;
+
+ public EndpointWrapper(FrozenDictionary<HttpMethod, EndpointWorkFunc> table, string path, ILogProvider log)
+ {
+ _wrappers = table;
+ InitPathAndLog(path, log);
+ }
+
+ protected override ValueTask<VfReturnType> OnProcessAsync(HttpEntity entity)
+ {
+ ref readonly EndpointWorkFunc func = ref _wrappers.GetValueRefOrNullRef(entity.Server.Method);
+
+ if (Unsafe.IsNullRef(in func))
+ {
+ return ValueTask.FromResult(VfReturnType.ProcessAsFile);
+ }
+
+ return func(entity);
+ }
}
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs b/lib/VNLib.Plugins.Extensions.Loading/src/S3Config.cs
index 11f101f..0574cb0 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) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -26,7 +26,12 @@ using System.Text.Json.Serialization;
namespace VNLib.Plugins.Extensions.Loading
{
- public sealed class S3Config
+
+ /// <summary>
+ /// A common json-serializable configuration for S3 storage
+ /// in an attempt to unify S3 configuration.
+ /// </summary>
+ public class S3Config
{
[JsonPropertyName("server_address")]
public string? ServerAddress { get; init; }
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs
index 885f22f..fae22c8 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/HCVaultClient.cs
@@ -163,7 +163,7 @@ namespace VNLib.Plugins.Extensions.Loading
using HttpResponseMessage response = await _client.SendAsync(ms, HttpCompletionOption.ResponseHeadersRead);
//Check if an error occured in the response
- await ProcessVaultErrorResponseAsync(response);
+ await ProcessVaultErrorResponseAsync(secretName, response);
//Read the response async
using SecretResponse res = await ReadSecretResponse(response.Content);
@@ -266,7 +266,7 @@ namespace VNLib.Plugins.Extensions.Loading
return null;
}
- private static ValueTask ProcessVaultErrorResponseAsync(HttpResponseMessage response)
+ private static ValueTask ProcessVaultErrorResponseAsync(string secretName, HttpResponseMessage response)
{
if (response.IsSuccessStatusCode)
{
@@ -278,7 +278,7 @@ namespace VNLib.Plugins.Extensions.Loading
if(!ctLen.HasValue || ctLen.Value == 0)
{
return ValueTask.FromException(
- new HttpRequestException($"Failed to fetch secret from vault with error code {response.StatusCode}")
+ new HttpRequestException($"Failed to fetch secret '{secretName}' from vault with error code {response.StatusCode}")
);
}
@@ -300,15 +300,15 @@ namespace VNLib.Plugins.Extensions.Loading
);
}
- return ExceptionsFromContentAsync(response);
+ return ExceptionsFromContentAsync(secretName, response);
- static ValueTask ExceptionFromVaultErrors(HttpStatusCode code, VaultErrorMessage? errs)
+ static ValueTask ExceptionFromVaultErrors(string secretName, HttpStatusCode code, VaultErrorMessage? errs)
{
//If the error message is null, raise an exception
if (errs?.Errors is null || errs.Errors.Length == 0)
{
return ValueTask.FromException(
- new HttpRequestException($"Failed to fetch secret from vault with error code {code}")
+ new HttpRequestException($"Failed to fetch secret '{secretName}' from vault with error code {code}")
);
}
@@ -318,17 +318,17 @@ namespace VNLib.Plugins.Extensions.Loading
//Finally raise the exception with all the returned errors
return ValueTask.FromException(
- new HttpRequestException($"Failed to fetch secre from vault with {code}, errors:\n {errStr}")
+ new HttpRequestException($"Failed to fetch secret `{secretName}` from vault with {code}, errors:\n {errStr}")
);
}
- static async ValueTask ExceptionsFromContentAsync(HttpResponseMessage response)
+ static async ValueTask ExceptionsFromContentAsync(string secretName, HttpResponseMessage response)
{
//Read stream async and deserialize async
using Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
VaultErrorMessage? errs = await JsonSerializer.DeserializeAsync<VaultErrorMessage>(stream);
- await ExceptionFromVaultErrors(response.StatusCode, errs);
+ await ExceptionFromVaultErrors(secretName, response.StatusCode, errs);
}
}
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 754bec6..d39cd98 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
@@ -48,7 +48,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="FluentValidation" Version="11.9.1" />
+ <PackageReference Include="FluentValidation" Version="11.9.2" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj
index 753deaf..b27fc00 100644
--- a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj
+++ b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj
@@ -49,7 +49,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj
index 97c6096..7015b8c 100644
--- a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj
+++ b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj
@@ -49,7 +49,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
</ItemGroup>