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 ?? ""; private readonly DatabaseConfig config = options.Get(DatabaseConfig.BlobStorage); private readonly IMemoryCache cache = memoryCache; private string GetFullPath(string fileName) { // Ensure the directory exists var directory = Path.Join(RootPath, config.SystemContainer, fileName); if (directory != null && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } return fileName; } public async Task DeleteAsync(string fileName) { 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)) { return; // No file to delete } try { File.Delete(path); } catch (Exception ex) { throw new InvalidOperationException($"Error deleting file {fileName}: {ex.Message}", ex); } } public Task GetStreamAsync(string fileName) { var path = GetFullPath(fileName); if (File.Exists(path)) { if (cache.TryGetValue(path, out Stream? cachedStream)) { return Task.FromResult(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 Task.FromResult(fileStream); } return Task.FromResult(null); } public async Task GetBytesAsync(string fileName) { var stream = await GetStreamAsync(fileName); if (stream != null) { using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); return memoryStream.ToArray(); } return null; } public async Task SaveAsync(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); await blobStream.CopyToAsync(fileStream); } public async Task UpdateAsync(Stream blobStream, string fileName) { var path = GetFullPath(fileName); if (File.Exists(path)) { await DeleteAsync(fileName); } await SaveAsync(blobStream, fileName); } }