/* * 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 { /// /// Represents a stream of arbitrary binary data /// public class Blob : BackingStream, IObjectStorage, IAsyncExclusiveResource { protected readonly LWStorageDescriptor Descriptor; /// /// The current blob's unique ID /// public string BlobId => Descriptor.DescriptorID; /// /// A value indicating if the has been modified /// public bool Modified { get; protected set; } /// /// A valid indicating if the blob was flagged for deletiong /// public bool Deleted { get; protected set; } /// /// The name of the file (does not change the actual file system name) /// public string Name { get => Descriptor.GetName(); set => Descriptor.SetName(value); } /// /// The UTC time the was last modified /// public DateTimeOffset LastWriteTimeUtc => Descriptor.LastModified; /// /// The UTC time the was created /// public DateTimeOffset CreationTimeUtc => Descriptor.Created; internal Blob(LWStorageDescriptor descriptor, in FileStream file) { this.Descriptor = descriptor; base.BaseStream = file; } /// /// Prevents other processes from reading from or writing to the /// /// The begining position of the range to lock /// The range to be locked /// /// /// [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("macos")] [UnsupportedOSPlatform("tvos")] public void Lock(long position, long length) => BaseStream.Lock(position, length); /// /// Prevents other processes from reading from or writing to the /// /// /// /// [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("macos")] [UnsupportedOSPlatform("tvos")] public void Lock() => BaseStream.Lock(0, BaseStream.Length); /// /// Allows access by other processes to all or part of the that was previously locked /// /// The begining position of the range to unlock /// The range to be unlocked /// [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("macos")] [UnsupportedOSPlatform("tvos")] public void Unlock(long position, long length) => BaseStream.Unlock(position, length); /// /// Allows access by other processes to the entire /// /// [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("macos")] [UnsupportedOSPlatform("tvos")] public void Unlock() => BaseStream.Unlock(0, BaseStream.Length); /// public override void SetLength(long value) { base.SetLength(value); //Set modified flag Modified |= true; } /* * Capture on-write calls to set the modified flag */ /// protected override void OnWrite(int count) => Modified |= true; T IObjectStorage.GetObject(string key) => ((IObjectStorage)Descriptor).GetObject(key); void IObjectStorage.SetObject(string key, T obj) => ((IObjectStorage)Descriptor).SetObject(key, obj); public string this[string index] { get => Descriptor[index]; set => Descriptor[index] = value; } /// /// Marks the file for deletion and will be deleted when the is disposed /// public void Delete() { //Set deleted flag Deleted |= true; Descriptor.Delete(); } /// public bool IsReleased => Descriptor.IsReleased; /// /// /// If the was opened with writing enabled, /// and file was modified, changes are flushed to the backing store /// and the stream is set to readonly. /// /// /// 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 /// /// /// A that may be awaited until the operation completes /// /// This method may be called to avoid flushing changes to the backing store /// when the is disposed (i.e. lifetime is manged outside of the desired scope) /// /// /// /// 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 */ /// public override async ValueTask DisposeAsync() { await ReleaseAsync(); GC.SuppressFinalize(this); } /// 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); } } } }