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