feat: add file management entities and services for chat message handling
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس ذخیرهسازی فایل در سیستم فایل محلی
|
||||
/// </summary>
|
||||
public class LocalFileStorageService : IFileStorageService
|
||||
{
|
||||
private readonly string _uploadBasePath;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public LocalFileStorageService(IConfiguration configuration,
|
||||
IHttpContextAccessor httpContextAccessor, IHostEnvironment env)
|
||||
{
|
||||
// محاسبه مسیر پایه: اگر env نبود، از مسیر فعلی استفاده کن
|
||||
var contentRoot = env.ContentRootPath;
|
||||
_uploadBasePath = Path.Combine(contentRoot, "Storage");
|
||||
// Base URL برای دسترسی به فایلها
|
||||
var request = httpContextAccessor.HttpContext?.Request;
|
||||
if (request != null)
|
||||
{
|
||||
_baseUrl = $"{request.Scheme}://{request.Host}/uploads";
|
||||
}
|
||||
else
|
||||
{
|
||||
_baseUrl = configuration["FileStorage:BaseUrl"] ?? "http://localhost:5000/uploads";
|
||||
}
|
||||
|
||||
// ایجاد پوشه اگر وجود نداشت
|
||||
if (!Directory.Exists(_uploadBasePath))
|
||||
{
|
||||
Directory.CreateDirectory(_uploadBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string StoragePath, string StorageUrl)> UploadAsync(
|
||||
Stream fileStream,
|
||||
string uniqueFileName,
|
||||
string category)
|
||||
{
|
||||
// ایجاد پوشه دستهبندی (مثلاً: Uploads/TaskChatMessage)
|
||||
var categoryPath = Path.Combine(_uploadBasePath, category);
|
||||
if (!Directory.Exists(categoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(categoryPath);
|
||||
}
|
||||
|
||||
// ایجاد زیرپوشه بر اساس تاریخ (مثلاً: Uploads/TaskChatMessage/2026/01)
|
||||
var datePath = Path.Combine(categoryPath, DateTime.Now.Year.ToString(),
|
||||
DateTime.Now.Month.ToString("00"));
|
||||
if (!Directory.Exists(datePath))
|
||||
{
|
||||
Directory.CreateDirectory(datePath);
|
||||
}
|
||||
|
||||
// مسیر کامل فایل
|
||||
var storagePath = Path.Combine(datePath, uniqueFileName);
|
||||
|
||||
// ذخیره فایل
|
||||
await using var fileStreamOutput = new FileStream(storagePath, FileMode.Create, FileAccess.Write);
|
||||
await fileStream.CopyToAsync(fileStreamOutput);
|
||||
|
||||
// URL فایل
|
||||
var relativePath = Path.GetRelativePath(_uploadBasePath, storagePath)
|
||||
.Replace("\\", "/");
|
||||
var storageUrl = $"{_baseUrl}/{relativePath}";
|
||||
|
||||
return (storagePath, storageUrl);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(string storagePath)
|
||||
{
|
||||
if (File.Exists(storagePath))
|
||||
{
|
||||
File.Delete(storagePath);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<Stream?> GetFileStreamAsync(string storagePath)
|
||||
{
|
||||
if (!File.Exists(storagePath))
|
||||
{
|
||||
return Task.FromResult<Stream?>(null);
|
||||
}
|
||||
|
||||
var stream = new FileStream(storagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return Task.FromResult<Stream?>(stream);
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(string storagePath)
|
||||
{
|
||||
return Task.FromResult(File.Exists(storagePath));
|
||||
}
|
||||
|
||||
public string GetFileUrl(string storagePath)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(_uploadBasePath, storagePath)
|
||||
.Replace("\\", "/");
|
||||
return $"{_baseUrl}/{relativePath}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس تولید thumbnail با استفاده از ImageSharp
|
||||
/// </summary>
|
||||
public class ThumbnailGeneratorService : IThumbnailGeneratorService
|
||||
{
|
||||
private readonly string _thumbnailBasePath;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public ThumbnailGeneratorService(IConfiguration configuration)
|
||||
{
|
||||
_thumbnailBasePath = configuration["FileStorage:ThumbnailPath"]
|
||||
?? Path.Combine(Directory.GetCurrentDirectory(), "Uploads", "Thumbnails");
|
||||
|
||||
_baseUrl = configuration["FileStorage:BaseUrl"] ?? "http://localhost:5000/uploads";
|
||||
|
||||
if (!Directory.Exists(_thumbnailBasePath))
|
||||
{
|
||||
Directory.CreateDirectory(_thumbnailBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string ThumbnailPath, string ThumbnailUrl)?> GenerateImageThumbnailAsync(
|
||||
string imagePath,
|
||||
int width = 200,
|
||||
int height = 200)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(imagePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// بارگذاری تصویر
|
||||
using var image = await Image.LoadAsync(imagePath);
|
||||
|
||||
// Resize با حفظ نسبت
|
||||
image.Mutate(x => x.Resize(new ResizeOptions
|
||||
{
|
||||
Size = new Size(width, height),
|
||||
Mode = ResizeMode.Max
|
||||
}));
|
||||
|
||||
// نام فایل thumbnail
|
||||
var fileName = Path.GetFileNameWithoutExtension(imagePath);
|
||||
var extension = Path.GetExtension(imagePath);
|
||||
var thumbnailFileName = $"{fileName}_thumb{extension}";
|
||||
|
||||
// مسیر ذخیره thumbnail
|
||||
var thumbnailPath = Path.Combine(_thumbnailBasePath, thumbnailFileName);
|
||||
|
||||
// ذخیره thumbnail با کیفیت 80
|
||||
await image.SaveAsync(thumbnailPath, new JpegEncoder { Quality = 80 });
|
||||
|
||||
// URL thumbnail
|
||||
var thumbnailUrl = $"{_baseUrl}/thumbnails/{thumbnailFileName}";
|
||||
|
||||
return (thumbnailPath, thumbnailUrl);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// در صورت خطا null برمیگردانیم
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string ThumbnailPath, string ThumbnailUrl)?> GenerateVideoThumbnailAsync(string videoPath)
|
||||
{
|
||||
// TODO: برای Video thumbnail باید از FFmpeg استفاده کنیم
|
||||
// فعلاً یک placeholder image برمیگردانیم
|
||||
await Task.CompletedTask;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task DeleteThumbnailAsync(string thumbnailPath)
|
||||
{
|
||||
if (File.Exists(thumbnailPath))
|
||||
{
|
||||
File.Delete(thumbnailPath);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<(int Width, int Height)?> GetImageDimensionsAsync(string imagePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(imagePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var imageInfo = await Image.IdentifyAsync(imagePath);
|
||||
return (imageInfo.Width, imageInfo.Height);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user