Files
ContentGen_BE/media-worker/Services/S3StorageService.cs
Harun CAN 85c35c73e8
Some checks failed
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled
main
2026-03-29 12:43:49 +03:00

173 lines
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SaasMediaWorker.Configuration;
using SaasMediaWorker.Models;
namespace SaasMediaWorker.Services;
/// <summary>
/// S3/Cloudflare R2 depolama servisi.
/// Üretilen medya dosyalarını nesne depolamaya yükler.
///
/// Neden Cloudflare R2?
/// - Egress (çıkış) ücretsiz — global SaaS için büyük maliyet avantajı
/// - S3 uyumlu API — AWS SDK ile doğrudan çalışır
/// - ARM64 uyumlu — AWS SDK .NET tamamen cross-platform
/// </summary>
public class S3StorageService
{
private readonly ILogger<S3StorageService> _logger;
private readonly S3Settings _settings;
private readonly AmazonS3Client _s3Client;
public S3StorageService(
ILogger<S3StorageService> logger,
IOptions<S3Settings> settings)
{
_logger = logger;
_settings = settings.Value;
var config = new AmazonS3Config
{
ServiceURL = _settings.Endpoint,
ForcePathStyle = true,
RequestChecksumCalculation = RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation = ResponseChecksumValidation.WHEN_REQUIRED,
};
_s3Client = new AmazonS3Client(
_settings.AccessKey,
_settings.SecretKey,
config);
}
/// <summary>
/// Tek bir dosyayı S3/R2'ye yükler.
/// Büyük dosyalar (>5MB) otomatik multipart upload kullanır.
/// </summary>
public async Task<(string url, string key)> UploadFileAsync(
string localPath,
string projectId,
string fileName,
string contentType,
CancellationToken ct)
{
var key = $"projects/{projectId}/{DateTime.UtcNow:yyyy/MM/dd}/{fileName}";
_logger.LogInformation(
"☁️ S3 yükleme başlıyor — Key: {Key}, Dosya: {File}",
key, Path.GetFileName(localPath));
var fileInfo = new FileInfo(localPath);
if (fileInfo.Length > 5 * 1024 * 1024) // 5MB üstü → multipart
{
await UploadMultipartAsync(localPath, key, contentType, ct);
}
else
{
var request = new PutObjectRequest
{
BucketName = _settings.BucketName,
Key = key,
FilePath = localPath,
ContentType = contentType,
DisablePayloadSigning = true,
};
await _s3Client.PutObjectAsync(request, ct);
}
var publicUrl = string.IsNullOrEmpty(_settings.PublicBaseUrl)
? $"{_settings.Endpoint}/{_settings.BucketName}/{key}"
: $"{_settings.PublicBaseUrl.TrimEnd('/')}/{key}";
_logger.LogInformation(
"✅ S3 yükleme tamamlandı — {Key} ({Size} bytes)",
key, fileInfo.Length);
return (publicUrl, key);
}
/// <summary>
/// Render pipeline sonucu üretilen tüm medya dosyalarını toplu yükler.
/// </summary>
public async Task<List<GeneratedMediaFile>> UploadAllMediaAsync(
string projectId,
List<GeneratedMediaFile> mediaFiles,
CancellationToken ct)
{
var uploadedFiles = new List<GeneratedMediaFile>();
foreach (var media in mediaFiles)
{
if (!File.Exists(media.LocalPath))
{
_logger.LogWarning("Dosya bulunamadı, atlanıyor: {Path}", media.LocalPath);
continue;
}
var fileName = Path.GetFileName(media.LocalPath);
var (url, key) = await UploadFileAsync(
media.LocalPath, projectId, fileName, media.MimeType, ct);
media.S3Url = url;
media.S3Key = key;
media.FileSizeBytes = new FileInfo(media.LocalPath).Length;
uploadedFiles.Add(media);
}
_logger.LogInformation(
"Toplu yükleme tamamlandı — {Count}/{Total} dosya yüklendi",
uploadedFiles.Count, mediaFiles.Count);
return uploadedFiles;
}
/// <summary>
/// Final videoyu yükler ve public URL döner.
/// </summary>
public async Task<string> UploadFinalVideoAsync(
string localPath,
string projectId,
CancellationToken ct)
{
var fileName = $"final_video_{DateTime.UtcNow:yyyyMMdd_HHmmss}.mp4";
var (url, _) = await UploadFileAsync(
localPath, projectId, fileName, "video/mp4", ct);
return url;
}
private async Task UploadMultipartAsync(
string localPath, string key, string contentType, CancellationToken ct)
{
using var transferUtility = new TransferUtility(_s3Client);
var request = new TransferUtilityUploadRequest
{
BucketName = _settings.BucketName,
Key = key,
FilePath = localPath,
ContentType = contentType,
PartSize = 5 * 1024 * 1024, // 5MB parçalar
DisablePayloadSigning = true,
};
request.UploadProgressEvent += (_, args) =>
{
if (args.PercentDone % 25 == 0)
{
_logger.LogInformation(
"Multipart upload: {Key} — %{Percent}",
key, args.PercentDone);
}
};
await transferUtility.UploadAsync(request, ct);
}
}