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 async ValueTask Get(Guid id) { 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); return ret; } catch (AmazonS3Exception aex) { _logger.LogError(aex, "Failed to get metadata for {Id}, {Error}", id, aex.Message); } return default; } /// public async ValueTask> Get(Guid[] ids) { var ret = new List(); foreach (var id in ids) { var r = await Get(id); if (r != null) { ret.Add(r); } } return ret; } public ValueTask Add(Database.File f) { return Set(f.Id, f); } /// public async ValueTask Update(Guid id, Database.File meta) { var oldMeta = await Get(id); if (oldMeta == default) return; oldMeta.Patch(meta); await Set(id, oldMeta); } /// public ValueTask> ListFiles(PagedRequest request) { 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 Get(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 async ValueTask Delete(Guid id) { await _client.DeleteObjectAsync(_config.BucketName, ToKey(id)); } private async ValueTask Set(Guid id, Database.File meta) { await _client.PutObjectAsync(new() { BucketName = _config.BucketName, Key = ToKey(id), ContentBody = JsonConvert.SerializeObject(meta), ContentType = "application/json" }); } private static string ToKey(Guid id) => $"metadata_{id}"; }