using System; using System.IO; using System.Threading.Tasks; namespace Nop.Plugin.Misc.FruitBankPlugin.Services.FileStorage { /// /// Local file system storage provider - saves files to wwwroot/uploads /// public class LocalFileStorageProvider : IFileStorageProvider { private readonly string _baseDirectory; public LocalFileStorageProvider(string baseDirectory = null) { // Default to wwwroot/uploads if not specified _baseDirectory = baseDirectory ?? Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads"); // Ensure base directory exists if (!Directory.Exists(_baseDirectory)) { Directory.CreateDirectory(_baseDirectory); } } /// /// Saves a file stream to local file system /// public async Task SaveFileAsync(Stream fileStream, string relativePath) { if (fileStream == null) throw new ArgumentNullException(nameof(fileStream)); if (string.IsNullOrWhiteSpace(relativePath)) throw new ArgumentNullException(nameof(relativePath)); var fullPath = Path.Combine(_baseDirectory, relativePath); var directory = Path.GetDirectoryName(fullPath); // Ensure directory exists if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // Save file using (var fileStreamOutput = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None)) { await fileStream.CopyToAsync(fileStreamOutput); } return fullPath; } /// /// Retrieves a file stream from local file system /// public async Task GetFileAsync(string relativePath) { if (string.IsNullOrWhiteSpace(relativePath)) throw new ArgumentNullException(nameof(relativePath)); var fullPath = Path.Combine(_baseDirectory, relativePath); if (!File.Exists(fullPath)) throw new FileNotFoundException($"File not found: {relativePath}", fullPath); // Read file into memory stream to avoid file locks var memoryStream = new MemoryStream(); using (var fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { await fileStream.CopyToAsync(memoryStream); } memoryStream.Position = 0; return memoryStream; } /// /// Deletes a file from local file system /// public Task DeleteFileAsync(string relativePath) { if (string.IsNullOrWhiteSpace(relativePath)) throw new ArgumentNullException(nameof(relativePath)); var fullPath = Path.Combine(_baseDirectory, relativePath); if (File.Exists(fullPath)) { File.Delete(fullPath); // Try to clean up empty directories CleanupEmptyDirectories(fullPath); return Task.FromResult(true); } return Task.FromResult(false); } /// /// Checks if a file exists in local file system /// public Task FileExistsAsync(string relativePath) { if (string.IsNullOrWhiteSpace(relativePath)) return Task.FromResult(false); var fullPath = Path.Combine(_baseDirectory, relativePath); return Task.FromResult(File.Exists(fullPath)); } /// /// Cleans up empty parent directories after file deletion /// private void CleanupEmptyDirectories(string filePath) { try { var directory = Path.GetDirectoryName(filePath); while (!string.IsNullOrEmpty(directory) && directory != _baseDirectory && Directory.Exists(directory) && Directory.GetFiles(directory).Length == 0 && Directory.GetDirectories(directory).Length == 0) { Directory.Delete(directory); directory = Path.GetDirectoryName(directory); } } catch { // Ignore cleanup errors - not critical } } } }