/*
* 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);
}
}
}
}