using back.Options; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; namespace back.persistance.blob; public class FileSystemImageStorageService( IOptions systemOptions, IOptionsMonitor options, IMemoryCache memoryCache ) : IBlobStorageService { private readonly string RootPath = systemOptions.Value.BaseDirectory ?? "data"; private readonly DatabaseConfig config = options.Get(DatabaseConfig.BlobStorage); private readonly IMemoryCache cache = memoryCache; private string GetFullPath(string fileName) { // Ensure the directory exists var path = Path.Join(RootPath, config.SystemContainer, fileName); var directory = Path.GetDirectoryName(path); if (directory != null && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } return path; } public async Task Delete(string fileName) { try { var path = GetFullPath(fileName); if (cache.TryGetValue(path, out Stream? cachedStream)) { cachedStream?.Dispose(); // Dispose the cached stream if it exists cache.Remove(path); // Remove from cache } if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { throw new InvalidOperationException($"Error deleting file {fileName}: {ex.Message}", ex); } } public async Task GetStream(string fileName) { var path = GetFullPath(fileName); if (File.Exists(path)) { if (cache.TryGetValue(path, out Stream? cachedStream)) { return cachedStream; } // open the file stream for multiple reads and cache it for performance var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); cache.CreateEntry(path) .SetValue(fileStream) .SetSlidingExpiration(TimeSpan.FromMinutes(30)); // Cache for 30 minutes return fileStream; } return null; } public async Task GetBytes(string fileName) { var stream = await GetStream(fileName); if (stream != null) { using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); return memoryStream.ToArray(); } return null; } public async Task Save(Stream blobStream, string fileName) { var path = GetFullPath(fileName); if (cache.TryGetValue(path, out Stream? _) || File.Exists(path)) { throw new InvalidOperationException($"File {fileName} already exists. Use Update for updating file info."); } using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); await blobStream.CopyToAsync(fileStream); } public async Task Update(Stream blobStream, string fileName) { var path = GetFullPath(fileName); if (File.Exists(path)) { await Delete(fileName); } await Save(blobStream, fileName); } }