diff options
Diffstat (limited to 'VNLib.Plugins.Extensions.Data/Storage')
11 files changed, 0 insertions, 1319 deletions
diff --git a/VNLib.Plugins.Extensions.Data/Storage/Blob.cs b/VNLib.Plugins.Extensions.Data/Storage/Blob.cs deleted file mode 100644 index ab18eeb..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/Blob.cs +++ /dev/null @@ -1,244 +0,0 @@ -/* -* Copyright (c) 2022 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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.IO; -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() - { - 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(); - } - } - } -}
\ No newline at end of file diff --git a/VNLib.Plugins.Extensions.Data/Storage/BlobExtensions.cs b/VNLib.Plugins.Extensions.Data/Storage/BlobExtensions.cs deleted file mode 100644 index 468a66d..0000000 --- a/VNLib.Plugins.Extensions.Data/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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://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/VNLib.Plugins.Extensions.Data/Storage/BlobStore.cs b/VNLib.Plugins.Extensions.Data/Storage/BlobStore.cs deleted file mode 100644 index 6897516..0000000 --- a/VNLib.Plugins.Extensions.Data/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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://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/VNLib.Plugins.Extensions.Data/Storage/LWDecriptorCreationException.cs b/VNLib.Plugins.Extensions.Data/Storage/LWDecriptorCreationException.cs deleted file mode 100644 index db0dbbb..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://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/VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs deleted file mode 100644 index d7f6e29..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs +++ /dev/null @@ -1,48 +0,0 @@ -/* -* Copyright (c) 2022 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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/. -*/ - -using Microsoft.EntityFrameworkCore; - -namespace VNLib.Plugins.Extensions.Data.Storage -{ -#nullable disable - internal sealed class LWStorageContext : TransactionalDbContext - { - 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) - { - //Set table name - modelBuilder.Entity<LWStorageEntry>() - .ToTable(TableName); - } - } -}
\ No newline at end of file diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs deleted file mode 100644 index 72665f3..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs +++ /dev/null @@ -1,230 +0,0 @@ -/* -* Copyright (c) 2022 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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.IO; -using System.Text.Json; -using System.Collections; -using System.IO.Compression; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Text.Json.Serialization; - -using VNLib.Utils; -using VNLib.Utils.Async; -using VNLib.Utils.Extensions; -using VNLib.Utils.Memory; - -namespace VNLib.Plugins.Extensions.Data.Storage -{ - /// <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; - - /// <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; - - ///<inheritdoc/> - protected override AsyncUpdateCallback UpdateCb { get; } - ///<inheritdoc/> - protected override AsyncDeleteCallback DeleteCb { get; } - ///<inheritdoc/> - protected override JsonSerializerOptions JSO => SerializerOptions; - - internal LWStorageDescriptor(LWStorageManager manager, LWStorageEntry entry) - { - Entry = entry; - UpdateCb = manager.UpdateDescriptorAsync; - DeleteCb = manager.RemoveDescriptorAsync; - StringStorage = new(OnStringStoreLoad); - } - - internal Dictionary<string, string> OnStringStoreLoad() - { - if(Entry.Data == null || Entry.Data.Length == 0) - { - return new(StringComparer.OrdinalIgnoreCase); - } - else - { - //Calc and alloc decode buffer - int bufferSize = (int)(Entry.Data.Length * 1.75); - using UnsafeMemoryHandle<byte> decodeBuffer = Memory.UnsafeAlloc<byte>(bufferSize); - - //Decode and deserialize the data - return BrotliDecoder.TryDecompress(Entry.Data, decodeBuffer, out int written) - ? JsonSerializer.Deserialize<Dictionary<string, string>>(Entry.Data, SerializerOptions) ?? new(StringComparer.OrdinalIgnoreCase) - : throw new InvalidDataException("Failed to decompress data"); - } - } - - /// <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) ? val.AsJsonObject<T>(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 = obj.ToJsonString(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() - { - await base.ReleaseAsync(); - //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() => StringStorage.Value; - } -}
\ No newline at end of file diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageEntry.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageEntry.cs deleted file mode 100644 index 5c5da61..0000000 --- a/VNLib.Plugins.Extensions.Data/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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://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/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs deleted file mode 100644 index 027fa90..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs +++ /dev/null @@ -1,347 +0,0 @@ -/* -* Copyright (c) 2022 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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.IO; -using System.Data; -using System.Linq; -using System.Threading; -using System.IO.Compression; -using System.Threading.Tasks; - -using Microsoft.EntityFrameworkCore; - -using VNLib.Utils; -using VNLib.Utils.IO; -using VNLib.Utils.Memory; - -namespace VNLib.Plugins.Extensions.Data.Storage -{ - - /// <summary> - /// Provides single table database object storage services - /// </summary> - public sealed class LWStorageManager - { - /// <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) - { - if (string.IsNullOrWhiteSpace(userId)) - { - throw new ArgumentNullException(nameof(userId)); - } - - //If no override id was specified, generate a new one - descriptorIdOverride ??= NewDescriptorIdGenerator(); - - DateTime createdOrModifedTime = DateTime.UtcNow; - - await using LWStorageContext ctx = GetContext(); - await ctx.OpenTransactionAsync(cancellation); - - //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.SaveChangesAsync(cancellation); - - if (!result) - { - //Rollback and raise exception - await ctx.RollbackTransctionAsync(cancellation); - throw new LWDescriptorCreationException("Failed to create descriptor, because changes could not be saved"); - } - else - { - //Commit transaction and return the new descriptor - await ctx.CommitTransactionAsync(cancellation); - return new LWStorageDescriptor(this, entry); - } - } - - /// <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) - { - //Allow null/empty entrys to just return null - if (string.IsNullOrWhiteSpace(userid)) - { - throw new ArgumentNullException(nameof(userid)); - } - - //Init db - await using LWStorageContext db = GetContext(); - //Begin transaction - await db.OpenTransactionAsync(cancellation); - //Get entry - LWStorageEntry? entry = await (from s in db.Descriptors - where s.UserId == userid - select s) - .SingleOrDefaultAsync(cancellation); - - //Close transactions and return - if (entry == null) - { - await db.RollbackTransctionAsync(cancellation); - return null; - } - else - { - await db.CommitTransactionAsync(cancellation); - return 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) - { - //Allow null/empty entrys to just return null - if (string.IsNullOrWhiteSpace(descriptorId)) - { - throw new ArgumentNullException(nameof(descriptorId)); - } - - //Init db - await using LWStorageContext db = GetContext(); - //Begin transaction - await db.OpenTransactionAsync(cancellation); - //Get entry - LWStorageEntry? entry = await (from s in db.Descriptors - where s.Id == descriptorId - select s) - .SingleOrDefaultAsync(cancellation); - - //Close transactions and return - if (entry == null) - { - await db.RollbackTransctionAsync(cancellation); - return null; - } - else - { - await db.CommitTransactionAsync(cancellation); - return 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(); - //Begin transaction - await db.OpenTransactionAsync(cancellation); - - //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); - - //Save changes - ERRNO count = await db.SaveChangesAsync(cancellation); - - //Commit transaction - await db.CommitTransactionAsync(cancellation); - - return count; - } - - /// <summary> - /// Updates a descriptor's data field - /// </summary> - /// <param name="descriptorObj">Descriptor to update</param> - /// <param name="data">Data string to store to descriptor record</param> - /// <exception cref="LWStorageUpdateFailedException"></exception> - internal async Task UpdateDescriptorAsync(object descriptorObj, Stream data) - { - LWStorageEntry entry = (descriptorObj as LWStorageDescriptor)!.Entry; - ERRNO result = 0; - try - { - await using LWStorageContext ctx = GetContext(); - await ctx.OpenTransactionAsync(CancellationToken.None); - - //Begin tracking - ctx.Descriptors.Attach(entry); - - //Convert stream to vnstream - VnMemoryStream vms = (VnMemoryStream)data; - using (IMemoryHandle<byte> encBuffer = Memory.SafeAlloc<byte>((int)vms.Length)) - { - //try to compress - if(!BrotliEncoder.TryCompress(vms.AsSpan(), encBuffer.Span, out int compressed)) - { - throw new InvalidDataException("Failed to compress the descriptor data"); - } - //Set the data - entry.Data = encBuffer.Span.ToArray(); - } - //Update modified time - entry.LastModified = DateTime.UtcNow; - - //Save changes - result = await ctx.SaveChangesAsync(CancellationToken.None); - - //Commit or rollback - if (result) - { - await ctx.CommitTransactionAsync(CancellationToken.None); - } - else - { - await ctx.RollbackTransctionAsync(CancellationToken.None); - } - } - 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"); - } - } - - /// <summary> - /// Function to remove the specified descriptor - /// </summary> - /// <param name="descriptorObj">The active descriptor to remove from the database</param> - /// <exception cref="LWStorageRemoveFailedException"></exception> - internal async Task RemoveDescriptorAsync(object descriptorObj) - { - LWStorageEntry descriptor = (descriptorObj as LWStorageDescriptor)!.Entry; - ERRNO result; - try - { - //Init db - await using LWStorageContext db = GetContext(); - //Begin transaction - await db.OpenTransactionAsync(); - - //Delete the user from the database - db.Descriptors.Remove(descriptor); - - //Save changes and commit if successful - result = await db.SaveChangesAsync(); - - if (result) - { - await db.CommitTransactionAsync(); - } - else - { - await db.RollbackTransctionAsync(); - } - } - 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/VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs deleted file mode 100644 index 806912c..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://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/VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs deleted file mode 100644 index fe555bf..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://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/VNLib.Plugins.Extensions.Data/Storage/UndefinedBlobStateException.cs b/VNLib.Plugins.Extensions.Data/Storage/UndefinedBlobStateException.cs deleted file mode 100644 index e845372..0000000 --- a/VNLib.Plugins.Extensions.Data/Storage/UndefinedBlobStateException.cs +++ /dev/null @@ -1,45 +0,0 @@ -/* -* Copyright (c) 2022 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 General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Plugins.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 -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.Runtime.Serialization; - -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) - {} - protected UndefinedBlobStateException(SerializationInfo info, StreamingContext context) : base(info, context) - {} - } -}
\ No newline at end of file |