namespace EchoHub.Server.Services; public class FileStorageService { private readonly string _storagePath; public FileStorageService(IConfiguration configuration) { _storagePath = configuration["Storage:Path"] ?? Path.Combine(AppContext.BaseDirectory, "uploads"); if (!Directory.Exists(_storagePath)) { Directory.CreateDirectory(_storagePath); } } public async Task<(string fileId, string filePath)> SaveFileAsync(Stream stream, string fileName) { var fileId = Guid.NewGuid().ToString(); var extension = Path.GetExtension(fileName); var storedFileName = $"{fileId}{extension}"; var filePath = Path.Combine(_storagePath, storedFileName); using var fileStream = File.Create(filePath); await stream.CopyToAsync(fileStream); return (fileId, filePath); } public string? GetFilePath(string fileId) { var files = Directory.GetFiles(_storagePath, $"{fileId}.*"); return files.Length > 0 ? files[0] : null; } /// /// Returns the set of stored file ids (filenames without extension) currently on disk. /// One directory scan, so callers can bulk-check many attachments without a glob per file. /// public HashSet GetStoredFileIds() { var ids = new HashSet(StringComparer.OrdinalIgnoreCase); if (!Directory.Exists(_storagePath)) return ids; foreach (var file in Directory.EnumerateFiles(_storagePath)) ids.Add(Path.GetFileNameWithoutExtension(file)); return ids; } public void DeleteFile(string fileId) { var filePath = GetFilePath(fileId); if (filePath is not null && File.Exists(filePath)) { File.Delete(filePath); } } }