using Microsoft.Extensions.Logging; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.GridFS; using DomainResults.Common; namespace DataProviders.Abstractions { /// /// /// /// public abstract class BucketDataProviderBase : DataProviderBase { /// /// /// /// /// public BucketDataProviderBase( ILogger logger, IMongoClient client ) : base (logger, client){ } #region Upload private protected (Guid?, IDomainResult) Upload(Guid siteId, Guid userId, BucketFile file, string bucketName) => UploadAsync(siteId, userId, file, bucketName).Result; private protected Task<(Guid?, IDomainResult)> UploadAsync(Guid siteId, Guid userId, BucketFile file, string bucketName) => UploadAsyncCore(siteId, userId, file, bucketName); #endregion #region Upload many private protected (List?, IDomainResult) UploadMany(Guid siteId, Guid userId, List files, string bucketName) => UploadAsync(siteId, userId, files, bucketName).Result; private protected Task<(List?, IDomainResult)> UploadAsync(Guid siteId, Guid userId, List files, string bucketName) => UploadManyAsyncCore(siteId, userId, files, bucketName); #endregion #region Download private protected (BucketFile?, IDomainResult) Download(Guid siteId, Guid userId, Guid fileId, string bucketName) => DownloadAsync(siteId, userId, fileId, bucketName).Result; private protected Task<(BucketFile?, IDomainResult)> DownloadAsync(Guid siteId, Guid userId, Guid fileId, string bucketName) => DownloadAsyncCore(siteId, userId, fileId, bucketName); #endregion #region Delete private protected IDomainResult DeleteOne(Guid siteId, Guid userId, Guid fileId, string bucketName) => DeleteOneAsync(siteId, userId, fileId, bucketName).Result; private protected Task DeleteOneAsync(Guid siteId, Guid userId, Guid fileId, string bucketName) => DeleteOneAsyncCore(siteId, userId, fileId, bucketName); #endregion #region Delete many private protected IDomainResult DeleteMany(Guid siteId, Guid userId, string bucketName) => DeleteManyAsync(siteId, userId, bucketName).Result; private protected Task DeleteManyAsync(Guid siteId, Guid userId, string bucketName) => DeleteManyAsyncCore(siteId, userId, bucketName); #endregion #region Core methods private GridFSBucket CreateBucket(string bucketName) => new GridFSBucket(_client.GetDatabase(_databaseName), new GridFSBucketOptions { BucketName = bucketName, ChunkSizeBytes = 1048576, // 1MB WriteConcern = WriteConcern.WMajority, ReadPreference = ReadPreference.Secondary }); private async Task<(Guid?, IDomainResult)> UploadAsyncCore(Guid siteId, Guid userId, BucketFile file, string bucketName) { var (list, result) = await UploadManyAsyncCore(siteId, userId, new List { file }, bucketName); if (!result.IsSuccess || list == null) return (null, result); return (list.First(), result); } public async Task<(List?, IDomainResult)> UploadManyAsyncCore(Guid siteId, Guid userId, List files, string bucketName) { var options = new GridFSUploadOptions { ChunkSizeBytes = 64512, // 63KB }; try { var bucket = CreateBucket(bucketName); var result = new List(); foreach (var file in files) { options.Metadata = new BsonDocument { { "siteId", $"{siteId}"}, { "userId", $"{userId}"}, { "fileName", file.Name }, { "contentType", file.ContentType } }; var fileId = Guid.NewGuid(); await bucket.UploadFromBytesAsync($"{fileId}", file.Bytes, options); result.Add(fileId); } return result.Count > 0 ? IDomainResult.Success(result) : IDomainResult.Failed?>(); } catch (Exception ex) { _logger.LogError(ex, "Bucket data provider error"); return IDomainResult.Failed?>(); } } private async Task<(BucketFile?, IDomainResult)> DownloadAsyncCore(Guid siteId, Guid userId, Guid fileId, string bucketName) { try { var filter = Builders.Filter.And( Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}"), Builders.Filter.Eq(x => x.Filename, $"{fileId}") ); var sort = Builders.Sort.Descending(x => x.UploadDateTime); var bucket = CreateBucket(bucketName); using var cursor = await bucket.FindAsync(filter, new GridFSFindOptions { Limit = 1, Sort = sort, }); // fileInfo either has the matching file information or is null var fileInfo = (await cursor.ToListAsync()).FirstOrDefault(); if (fileInfo == null) return IDomainResult.NotFound(); var fileName = fileInfo.Metadata["fileName"].ToString() ?? ""; if (fileName == null) return IDomainResult.Failed(); var contentType = fileInfo.Metadata["contentType"].ToString() ?? ""; if (contentType == null) return IDomainResult.Failed(); var bytes = await bucket.DownloadAsBytesByNameAsync($"{fileId}", new GridFSDownloadByNameOptions { Revision = -1 }); if(bytes == null) return IDomainResult.Failed(); return IDomainResult.Success(new BucketFile(fileName, bytes, contentType)); } catch (Exception ex) { _logger.LogError(ex, "Bucket data provider error"); return IDomainResult.Failed(); } } private Task DeleteOneAsyncCore(Guid siteId, Guid userId, Guid fileId, string bucketName) => DeleteAsyncCore(siteId, userId, Builders.Filter.And( Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}"), Builders.Filter.Eq(x => x.Filename, $"{fileId}") ), bucketName); private Task DeleteManyAsyncCore(Guid siteId, Guid userId, string bucketName) => DeleteAsyncCore(siteId, userId, Builders.Filter.And( Builders.Filter.Eq(x => x.Metadata["siteId"], $"{siteId}"), Builders.Filter.Eq(x => x.Metadata["userId"], $"{userId}") ), bucketName); private async Task DeleteAsyncCore(Guid siteId, Guid userId, FilterDefinition filter, string bucketName) { try { var bucket = CreateBucket(bucketName); using var cursor = await bucket.FindAsync(filter); var count = 0; foreach (var fileInfo in await cursor.ToListAsync()) { await bucket.DeleteAsync(fileInfo.Id); count++; } return count > 0 ? IDomainResult.Success() : IDomainResult.NotFound(); } catch (Exception ex) { _logger.LogError(ex, "Bucket data provider error"); return IDomainResult.Failed(); } } #endregion } }