using Amazon.S3; using Newtonsoft.Json; using VoidCat.Model; using VoidCat.Services.Abstractions; namespace VoidCat.Services.Files; /// public class S3FileMetadataStore : IFileMetadataStore { private readonly ILogger _logger; private readonly AmazonS3Client _client; private readonly S3BlobConfig _config; public S3FileMetadataStore(S3BlobConfig settings, ILogger logger) { _logger = logger; _config = settings; _client = _config.CreateClient(); } /// public string? Key => _config.Name; /// public ValueTask Get(Guid id) where TMeta : VoidFileMeta { return GetMeta(id); } /// public async ValueTask> Get(Guid[] ids) where TMeta : VoidFileMeta { var ret = new List(); foreach (var id in ids) { var r = await GetMeta(id); if (r != null) { ret.Add(r); } } return ret; } /// public async ValueTask Update(Guid id, TMeta meta) where TMeta : VoidFileMeta { var oldMeta = await GetMeta(id); if (oldMeta == default) return; oldMeta.Description = meta.Description ?? oldMeta.Description; oldMeta.Name = meta.Name ?? oldMeta.Name; oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType; oldMeta.Storage = meta.Storage ?? oldMeta.Storage; oldMeta.Expires = meta.Expires; await Set(id, oldMeta); } /// public ValueTask> ListFiles(PagedRequest request) where TMeta : VoidFileMeta { async IAsyncEnumerable Enumerate() { var obj = await _client.ListObjectsV2Async(new() { BucketName = _config.BucketName, Prefix = "metadata_", MaxKeys = 5_000 }); foreach (var file in obj.S3Objects) { if (Guid.TryParse(file.Key.Split("metadata_")[1], out var id)) { var meta = await GetMeta(id); if (meta != default) { yield return meta; } } } } return ValueTask.FromResult(new PagedResult { Page = request.Page, PageSize = request.PageSize, Results = Enumerate().Skip(request.PageSize * request.Page).Take(request.PageSize) }); } /// public async ValueTask Stats() { var files = await ListFiles(new(0, Int32.MaxValue)); var count = await files.Results.CountAsync(); var size = await files.Results.SumAsync(a => (long) a.Size); return new(count, (ulong) size); } /// public ValueTask Get(Guid id) { return GetMeta(id); } /// public ValueTask GetPrivate(Guid id) { return GetMeta(id); } /// public async ValueTask Set(Guid id, SecretVoidFileMeta meta) { await _client.PutObjectAsync(new() { BucketName = _config.BucketName, Key = ToKey(id), ContentBody = JsonConvert.SerializeObject(meta), ContentType = "application/json" }); } /// public async ValueTask Delete(Guid id) { await _client.DeleteObjectAsync(_config.BucketName, ToKey(id)); } private async ValueTask GetMeta(Guid id) where TMeta : VoidFileMeta { try { var obj = await _client.GetObjectAsync(_config.BucketName, ToKey(id)); using var sr = new StreamReader(obj.ResponseStream); var json = await sr.ReadToEndAsync(); var ret = JsonConvert.DeserializeObject(json); if (ret != default) { ret.Id = id; } return ret; } catch (AmazonS3Exception aex) { _logger.LogError(aex, "Failed to get metadata for {Id}, {Error}", id, aex.Message); } return default; } private static string ToKey(Guid id) => $"metadata_{id}"; }