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
}
}