/*
* 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
{
///
/// Stores s to the local file system backed with a single table
/// that tracks changes
///
public class BlobStore
{
///
/// The root directory all blob files are stored
///
public DirectoryInfo RootDir { get; }
///
/// The backing store for blob meta-data
///
protected LWStorageManager BlobTable { get; }
///
/// Creates a new that accesses files
/// within the specified root directory.
///
/// The root directory containing the blob file contents
/// The db backing store
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);
}
///
/// Opens an existing from the current store
///
/// The id of the file being requested
/// Access level of the file
/// The sharing option of the underlying file
/// The size of the file buffer
/// If found, the requested , null otherwise. Throws exceptions if the file is opened in a non-sharable state
///
///
///
///
///
public virtual async Task 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;
}
}
///
/// Creates a new for the specified file sharing permissions
///
/// The name of the original file
/// The blob sharing permissions
///
/// The newly created
///
///
///
///
public virtual async Task 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;
}
}
}
}