using Amazon.S3; using Amazon.S3.Model; using Pldpro.Web.Services.Models; namespace Pldpro.Web.Services; public sealed class S3StorageService(IAmazonS3 s3) : IStorageService { private readonly IAmazonS3 _s3 = s3; public async Task> ListBucketsAsync(CancellationToken ct = default) { var resp = await _s3.ListBucketsAsync(ct); return resp.Buckets.Select(b => new BucketItem(b.BucketName, b.CreationDate)); } public async Task CreateBucketAsync(string bucketName, CancellationToken ct = default) { // Für S3-kompatible Endpoints reicht häufig nur der Name var req = new PutBucketRequest { BucketName = bucketName }; await _s3.PutBucketAsync(req, ct); } public async Task> ListObjectsAsync(string bucket, CancellationToken ct = default) { var items = new List(); string? token = null; do { var resp = await _s3.ListObjectsV2Async(new ListObjectsV2Request { BucketName = bucket, ContinuationToken = token }, ct); if (resp.S3Objects != null) { items.AddRange(resp.S3Objects.Select(o => new ObjectItem(o.Key, o.Size, o.LastModified))); token = (bool)resp.IsTruncated ? resp.NextContinuationToken : null; } } while (token is not null); return items; } public async Task UploadObjectAsync(string bucket, string key, Stream content, string contentType, CancellationToken ct = default) { var req = new PutObjectRequest { BucketName = bucket, Key = key, InputStream = content, ContentType = contentType }; await _s3.PutObjectAsync(req, ct); } public async Task<(Stream Stream, string ContentType, long? ContentLength)> GetObjectAsync( string bucket, string key, CancellationToken ct = default) { var resp = await _s3.GetObjectAsync(new GetObjectRequest { BucketName = bucket, Key = key }, ct); // ResponseStream NICHT kopieren, sondern direkt zurückgeben (Server streamt es weiter) var contentType = string.IsNullOrWhiteSpace(resp.Headers.ContentType) ? "application/octet-stream" : resp.Headers.ContentType; long? len = resp.Headers.ContentLength >= 0 ? resp.Headers.ContentLength : null; return (resp.ResponseStream, contentType, len); } public async Task DeleteObjectAsync(string bucket, string key) { // S3-Delete ist idempotent: 204 auch wenn das Objekt nicht existiert. await _s3.DeleteObjectAsync(new Amazon.S3.Model.DeleteObjectRequest { BucketName = bucket, Key = key }); } }