diff options
author | vnugent <public@vaughnnugent.com> | 2024-07-27 20:50:12 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-07-27 20:50:12 -0400 |
commit | d52772c010b10357d194239056d19c0d22d414fe (patch) | |
tree | 1d612391d61eec76a848d871a1e284da022e81e7 /lib/VNLib.Plugins.Extensions.Data/src | |
parent | c8567e58dc1d4135da1f6cefa6fa66af5fcd7b19 (diff) |
update secrets and remove deprecated and unused apis
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Data/src')
18 files changed, 4 insertions, 2080 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 |