From 915e5c7956fae342c638f36ae61ec49f8ac38b3a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 2 Apr 2019 21:54:29 +0200 Subject: [PATCH] Asset improvements. --- .../Assets/AzureBlobAssetStore.cs | 85 +++++-------- .../Assets/GoogleCloudAssetStore.cs | 70 +++-------- .../Assets/MongoGridFsAssetStore.cs | 74 +++++------- .../Assets/AssetStoreExtensions.cs | 74 ++++++++++++ .../Assets/FolderAssetStore.cs | 84 +++++-------- .../Assets/IAssetStore.cs | 12 +- .../Assets/MemoryAssetStore.cs | 40 ++----- .../Assets/NoopAssetStore.cs | 18 +-- .../Assets/AssetCommandMiddlewareTests.cs | 6 +- .../Assets/AssetExtensionTests.cs | 112 ++++++++++++++++++ .../Assets/AssetStoreTests.cs | 70 +++++------ .../Assets/AzureBlobAssetStoreTests.cs | 4 +- .../Assets/FolderAssetStoreTests.cs | 2 +- .../Assets/GoogleCloudAssetStoreTests.cs | 2 +- .../Assets/MemoryAssetStoreTests.cs | 2 +- .../Assets/MongoGridFsAssetStoreTests.cs | 2 +- 16 files changed, 346 insertions(+), 311 deletions(-) create mode 100644 src/Squidex.Infrastructure/Assets/AssetStoreExtensions.cs create mode 100644 tests/Squidex.Infrastructure.Tests/Assets/AssetExtensionTests.cs diff --git a/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs b/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs index e09a88879..c50b554a1 100644 --- a/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs +++ b/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs @@ -48,28 +48,31 @@ namespace Squidex.Infrastructure.Assets } } - public string GeneratePublicUrl(string id, long version, string suffix) + public string GeneratePublicUrl(string fileName) { + Guard.NotNullOrEmpty(fileName, nameof(fileName)); + if (blobContainer.Properties.PublicAccess != BlobContainerPublicAccessType.Blob) { - var sourceName = GetObjectName(id, version, suffix); - var sourceBlob = blobContainer.GetBlockBlobReference(sourceName); + var blob = blobContainer.GetBlockBlobReference(fileName); - return sourceBlob.Uri.ToString(); + return blob.Uri.ToString(); } return null; } - public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) { - var targetName = GetObjectName(id, version, suffix); - var targetBlob = blobContainer.GetBlobReference(targetName); - - var sourceBlob = blobContainer.GetBlockBlobReference(sourceFileName); + Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); + Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); try { + var sourceBlob = blobContainer.GetBlockBlobReference(sourceFileName); + + var targetBlob = blobContainer.GetBlobReference(targetFileName); + await targetBlob.StartCopyAsync(sourceBlob.Uri, null, AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); while (targetBlob.CopyState.Status == CopyStatus.Pending) @@ -87,7 +90,7 @@ namespace Squidex.Infrastructure.Assets } catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) { - throw new AssetAlreadyExistsException(targetName); + throw new AssetAlreadyExistsException(targetFileName); } catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) { @@ -95,79 +98,45 @@ namespace Squidex.Infrastructure.Assets } } - public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) { - var sourceName = GetObjectName(id, version, suffix); - var sourceBlob = blobContainer.GetBlockBlobReference(sourceName); + Guard.NotNullOrEmpty(fileName, nameof(fileName)); try { - await sourceBlob.DownloadToStreamAsync(stream, null, null, null, ct); + var blob = blobContainer.GetBlockBlobReference(fileName); + + await blob.DownloadToStreamAsync(stream, null, null, null, ct); } catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) { - throw new AssetNotFoundException($"Id={id}, Version={version}", ex); + throw new AssetNotFoundException(fileName, ex); } } - public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) - { - return UploadCoreAsync(GetObjectName(id, version, suffix), stream, overwrite, ct); - } - - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) - { - return UploadCoreAsync(fileName, stream, false, ct); - } - - public Task DeleteAsync(string id, long version, string suffix) - { - return DeleteCoreAsync(GetObjectName(id, version, suffix)); - } - - public Task DeleteAsync(string fileName) - { - return DeleteCoreAsync(fileName); - } - - private Task DeleteCoreAsync(string blobName) + public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) { - var blob = blobContainer.GetBlockBlobReference(blobName); - - return blob.DeleteIfExistsAsync(); - } + Guard.NotNullOrEmpty(fileName, nameof(fileName)); - private async Task UploadCoreAsync(string blobName, Stream stream, bool overwrite = false, CancellationToken ct = default) - { try { - var tempBlob = blobContainer.GetBlockBlobReference(blobName); + var tempBlob = blobContainer.GetBlockBlobReference(fileName); await tempBlob.UploadFromStreamAsync(stream, overwrite ? null : AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); } catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) { - throw new AssetAlreadyExistsException(blobName); + throw new AssetAlreadyExistsException(fileName); } } - private string GetObjectName(string id, long version, string suffix) + public Task DeleteAsync(string fileName) { - Guard.NotNullOrEmpty(id, nameof(id)); + Guard.NotNullOrEmpty(fileName, nameof(fileName)); - if (blobContainer == null) - { - throw new InvalidOperationException("No connection established yet."); - } + var blob = blobContainer.GetBlockBlobReference(fileName); - var name = $"{id}_{version}"; - - if (!string.IsNullOrWhiteSpace(suffix)) - { - name += "_" + suffix; - } - - return name; + return blob.DeleteIfExistsAsync(); } } } diff --git a/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs b/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs index f76589938..ab8207c8b 100644 --- a/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs +++ b/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs @@ -43,18 +43,19 @@ namespace Squidex.Infrastructure.Assets } } - public string GeneratePublicUrl(string id, long version, string suffix) + public string GeneratePublicUrl(string fileName) { return null; } - public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) { - var objectName = GetObjectName(id, version, suffix); + Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); + Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); try { - await storageClient.CopyObjectAsync(bucketName, sourceFileName, bucketName, objectName, IfNotExistsCopy, ct); + await storageClient.CopyObjectAsync(bucketName, sourceFileName, bucketName, targetFileName, IfNotExistsCopy, ct); } catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound) { @@ -62,85 +63,50 @@ namespace Squidex.Infrastructure.Assets } catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.PreconditionFailed) { - throw new AssetAlreadyExistsException(objectName); + throw new AssetAlreadyExistsException(targetFileName); } } - public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) { - var objectName = GetObjectName(id, version, suffix); + Guard.NotNullOrEmpty(fileName, nameof(fileName)); try { - await storageClient.DownloadObjectAsync(bucketName, objectName, stream, cancellationToken: ct); + await storageClient.DownloadObjectAsync(bucketName, fileName, stream, cancellationToken: ct); } catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound) { - throw new AssetNotFoundException($"Id={id}, Version={version}", ex); + throw new AssetNotFoundException(fileName, ex); } } - public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) + public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) { - return UploadCoreAsync(GetObjectName(id, version, suffix), stream, overwrite, ct); - } - - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) - { - return UploadCoreAsync(fileName, stream, false, ct); - } - - public Task DeleteAsync(string id, long version, string suffix) - { - return DeleteCoreAsync(GetObjectName(id, version, suffix)); - } - - public Task DeleteAsync(string fileName) - { - return DeleteCoreAsync(fileName); - } + Guard.NotNullOrEmpty(fileName, nameof(fileName)); - private async Task UploadCoreAsync(string objectName, Stream stream, bool overwrite = false, CancellationToken ct = default) - { try { - await storageClient.UploadObjectAsync(bucketName, objectName, "application/octet-stream", stream, overwrite ? null : IfNotExists, ct); + await storageClient.UploadObjectAsync(bucketName, fileName, "application/octet-stream", stream, overwrite ? null : IfNotExists, ct); } catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.PreconditionFailed) { - throw new AssetAlreadyExistsException(objectName); + throw new AssetAlreadyExistsException(fileName); } } - private async Task DeleteCoreAsync(string objectName) + public async Task DeleteAsync(string fileName) { + Guard.NotNullOrEmpty(fileName, nameof(fileName)); + try { - await storageClient.DeleteObjectAsync(bucketName, objectName); + await storageClient.DeleteObjectAsync(bucketName, fileName); } catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound) { return; } } - - private string GetObjectName(string id, long version, string suffix) - { - Guard.NotNullOrEmpty(id, nameof(id)); - - if (storageClient == null) - { - throw new InvalidOperationException("No connection established yet."); - } - - var name = GetFileName(id, version, suffix); - - return name; - } - - private static string GetFileName(string id, long version, string suffix) - { - return StringExtensions.JoinNonEmpty("_", id, version.ToString(), suffix); - } } } diff --git a/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs b/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs index 997fd9068..3a86d4fd9 100644 --- a/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs @@ -39,20 +39,20 @@ namespace Squidex.Infrastructure.Assets } } - public string GeneratePublicUrl(string id, long version, string suffix) + public string GeneratePublicUrl(string fileName) { return null; } - public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) { try { - var target = GetFileName(id, version, suffix); + var sourceName = GetFileName(sourceFileName, nameof(sourceFileName)); using (var readStream = await bucket.OpenDownloadStreamAsync(sourceFileName, cancellationToken: ct)) { - await UploadFileCoreAsync(target, readStream, false, ct); + await UploadAsync(targetFileName, readStream, false, ct); } } catch (GridFSFileNotFoundException ex) @@ -61,11 +61,11 @@ namespace Squidex.Infrastructure.Assets } } - public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) { try { - var name = GetFileName(id, version, suffix); + var name = GetFileName(fileName, nameof(fileName)); using (var readStream = await bucket.OpenDownloadStreamAsync(name, cancellationToken: ct)) { @@ -74,66 +74,52 @@ namespace Squidex.Infrastructure.Assets } catch (GridFSFileNotFoundException ex) { - throw new AssetNotFoundException($"Id={id}, Version={version}", ex); + throw new AssetNotFoundException(fileName, ex); } } - public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) + public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) { - return UploadFileCoreAsync(GetFileName(id, version, suffix), stream, overwrite, ct); - } - - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) - { - return UploadFileCoreAsync(fileName, stream, false, ct); - } - - public Task DeleteAsync(string id, long version, string suffix) - { - return DeleteCoreAsync(GetFileName(id, version, suffix)); - } + try + { + var name = GetFileName(fileName, nameof(fileName)); - public Task DeleteAsync(string fileName) - { - return DeleteCoreAsync(fileName); - } + if (overwrite) + { + await DeleteAsync(fileName); + } - private async Task DeleteCoreAsync(string id) - { - try + await bucket.UploadFromStreamAsync(fileName, fileName, stream, cancellationToken: ct); + } + catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { - await bucket.DeleteAsync(id); + throw new AssetAlreadyExistsException(fileName); } - catch (GridFSFileNotFoundException) + catch (MongoBulkWriteException ex) when (ex.WriteErrors.Any(x => x.Category == ServerErrorCategory.DuplicateKey)) { - return; + throw new AssetAlreadyExistsException(fileName); } } - private async Task UploadFileCoreAsync(string id, Stream stream, bool overwrite = false, CancellationToken ct = default) + public async Task DeleteAsync(string fileName) { try { - if (overwrite) - { - await bucket.DeleteAsync(id, ct); - } + var name = GetFileName(fileName, nameof(fileName)); - await bucket.UploadFromStreamAsync(id, id, stream, cancellationToken: ct); + await bucket.DeleteAsync(name); } - catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) - { - throw new AssetAlreadyExistsException(id); - } - catch (MongoBulkWriteException ex) when (ex.WriteErrors.Any(x => x.Category == ServerErrorCategory.DuplicateKey)) + catch (GridFSFileNotFoundException) { - throw new AssetAlreadyExistsException(id); + return; } } - private static string GetFileName(string id, long version, string suffix) + private static string GetFileName(string fileName, string parameterName) { - return StringExtensions.JoinNonEmpty("_", id, version.ToString(), suffix); + Guard.NotNullOrEmpty(fileName, parameterName); + + return fileName; } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Assets/AssetStoreExtensions.cs b/src/Squidex.Infrastructure/Assets/AssetStoreExtensions.cs new file mode 100644 index 000000000..a8824c314 --- /dev/null +++ b/src/Squidex.Infrastructure/Assets/AssetStoreExtensions.cs @@ -0,0 +1,74 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Assets +{ + public static class AssetStoreExtensions + { + public static string GeneratePublicUrl(this IAssetStore store, Guid id, long version, string suffix) + { + return store.GeneratePublicUrl(id.ToString(), version, suffix); + } + + public static string GeneratePublicUrl(this IAssetStore store, string id, long version, string suffix) + { + return store.GeneratePublicUrl(GetFileName(id, version, suffix)); + } + + public static Task CopyAsync(this IAssetStore store, string sourceFileName, Guid id, long version, string suffix, CancellationToken ct = default) + { + return store.CopyAsync(sourceFileName, id.ToString(), version, suffix, ct); + } + + public static Task CopyAsync(this IAssetStore store, string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + { + return store.CopyAsync(sourceFileName, GetFileName(id, version, suffix), ct); + } + + public static Task DownloadAsync(this IAssetStore store, Guid id, long version, string suffix, Stream stream, CancellationToken ct = default) + { + return store.DownloadAsync(id.ToString(), version, suffix, stream, ct); + } + + public static Task DownloadAsync(this IAssetStore store, string id, long version, string suffix, Stream stream, CancellationToken ct = default) + { + return store.DownloadAsync(GetFileName(id, version, suffix), stream, ct); + } + + public static Task UploadAsync(this IAssetStore store, Guid id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) + { + return store.UploadAsync(id.ToString(), version, suffix, stream, overwrite, ct); + } + + public static Task UploadAsync(this IAssetStore store, string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) + { + return store.UploadAsync(GetFileName(id, version, suffix), stream, overwrite, ct); + } + + public static Task DeleteAsync(this IAssetStore store, Guid id, long version, string suffix) + { + return store.DeleteAsync(id.ToString(), version, suffix); + } + + public static Task DeleteAsync(this IAssetStore store, string id, long version, string suffix) + { + return store.DeleteAsync(GetFileName(id, version, suffix)); + } + + public static string GetFileName(string id, long version, string suffix = null) + { + Guard.NotNullOrEmpty(id, nameof(id)); + + return StringExtensions.JoinNonEmpty("_", id, version.ToString(), suffix); + } + } +} diff --git a/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs b/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs index a61e24366..3df128be3 100644 --- a/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs +++ b/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs @@ -51,43 +51,28 @@ namespace Squidex.Infrastructure.Assets } } - public string GeneratePublicUrl(string id, long version, string suffix) + public string GeneratePublicUrl(string fileName) { return null; } - public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + public Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) { - var file = GetFile(id, version, suffix); + Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); + Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); - try - { - using (var fileStream = file.OpenRead()) - { - await fileStream.CopyToAsync(stream, BufferSize, ct); - } - } - catch (FileNotFoundException ex) - { - throw new AssetNotFoundException($"Id={id}, Version={version}", ex); - } - } - - public Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) - { - var targetFile = GetFile(id, version, suffix); + var targetFile = GetFile(targetFileName); + var sourceFile = GetFile(sourceFileName); try { - var file = GetFile(sourceFileName); - - file.CopyTo(targetFile.FullName); + sourceFile.CopyTo(targetFile.FullName); return TaskHelper.Done; } catch (IOException) when (targetFile.Exists) { - throw new AssetAlreadyExistsException(targetFile.Name); + throw new AssetAlreadyExistsException(targetFileName); } catch (FileNotFoundException ex) { @@ -95,35 +80,31 @@ namespace Squidex.Infrastructure.Assets } } - public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) - { - return UploadCoreAsync(GetFile(id, version, suffix), stream, overwrite, ct); - } - - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) + public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) { - return UploadCoreAsync(GetFile(fileName), stream, false, ct); - } + Guard.NotNullOrEmpty(fileName, nameof(fileName)); - public Task DeleteAsync(string id, long version, string suffix) - { - return DeleteFileCoreAsync(GetFile(id, version, suffix)); - } + var file = GetFile(fileName); - public Task DeleteAsync(string fileName) - { - return DeleteFileCoreAsync(GetFile(fileName)); + try + { + using (var fileStream = file.OpenRead()) + { + await fileStream.CopyToAsync(stream, BufferSize, ct); + } + } + catch (FileNotFoundException ex) + { + throw new AssetNotFoundException(fileName, ex); + } } - private static Task DeleteFileCoreAsync(FileInfo file) + public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) { - file.Delete(); + Guard.NotNullOrEmpty(fileName, nameof(fileName)); - return TaskHelper.Done; - } + var file = GetFile(fileName); - private static async Task UploadCoreAsync(FileInfo file, Stream stream, bool overwrite = false, CancellationToken ct = default) - { try { using (var fileStream = file.Open(overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.Write)) @@ -137,11 +118,15 @@ namespace Squidex.Infrastructure.Assets } } - private FileInfo GetFile(string id, long version, string suffix) + public Task DeleteAsync(string fileName) { - Guard.NotNullOrEmpty(id, nameof(id)); + Guard.NotNullOrEmpty(fileName, nameof(fileName)); + + var file = GetFile(fileName); - return GetFile(GetPath(id, version, suffix)); + file.Delete(); + + return TaskHelper.Done; } private FileInfo GetFile(string fileName) @@ -155,10 +140,5 @@ namespace Squidex.Infrastructure.Assets { return Path.Combine(directory.FullName, name); } - - private string GetPath(string id, long version, string suffix) - { - return Path.Combine(directory.FullName, StringExtensions.JoinNonEmpty("_", id, version.ToString(), suffix)); - } } } diff --git a/src/Squidex.Infrastructure/Assets/IAssetStore.cs b/src/Squidex.Infrastructure/Assets/IAssetStore.cs index 65d3c4f84..207a626f8 100644 --- a/src/Squidex.Infrastructure/Assets/IAssetStore.cs +++ b/src/Squidex.Infrastructure/Assets/IAssetStore.cs @@ -13,18 +13,14 @@ namespace Squidex.Infrastructure.Assets { public interface IAssetStore { - string GeneratePublicUrl(string id, long version, string suffix); + string GeneratePublicUrl(string fileName); - Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default); + Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default); - Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default); + Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default); - Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default); - - Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default); + Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default); Task DeleteAsync(string fileName); - - Task DeleteAsync(string id, long version, string suffix); } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs b/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs index a4900ad84..c5c2cbce3 100644 --- a/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs +++ b/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs @@ -19,15 +19,15 @@ namespace Squidex.Infrastructure.Assets private readonly AsyncLock readerLock = new AsyncLock(); private readonly AsyncLock writerLock = new AsyncLock(); - public string GeneratePublicUrl(string id, long version, string suffix) + public string GeneratePublicUrl(string fileName) { return null; } - public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) { Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); - Guard.NotNullOrEmpty(id, nameof(id)); + Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); if (!streams.TryGetValue(sourceFileName, out var sourceStream)) { @@ -36,15 +36,13 @@ namespace Squidex.Infrastructure.Assets using (await readerLock.LockAsync()) { - await UploadAsync(id, version, suffix, sourceStream, false, ct); + await UploadAsync(targetFileName, sourceStream, false, ct); } } - public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) { - Guard.NotNullOrEmpty(id, nameof(id)); - - var fileName = GetFileName(id, version, suffix); + Guard.NotNullOrEmpty(fileName, nameof(fileName)); if (!streams.TryGetValue(fileName, out var sourceStream)) { @@ -64,20 +62,10 @@ namespace Squidex.Infrastructure.Assets } } - public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) - { - Guard.NotNullOrEmpty(id, nameof(id)); - - return UploadCoreAsync(GetFileName(id, version, suffix), stream, overwrite, ct); - } - - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) + public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) { - return UploadCoreAsync(fileName, stream, false, ct); - } + Guard.NotNullOrEmpty(fileName, nameof(fileName)); - private async Task UploadCoreAsync(string fileName, Stream stream, bool overwrite, CancellationToken ct = default) - { var memoryStream = new MemoryStream(); async Task CopyAsync() @@ -111,13 +99,6 @@ namespace Squidex.Infrastructure.Assets } } - public Task DeleteAsync(string id, long version, string suffix) - { - Guard.NotNullOrEmpty(id, nameof(id)); - - return DeleteAsync(GetFileName(id, version, suffix)); - } - public Task DeleteAsync(string fileName) { Guard.NotNullOrEmpty(fileName, nameof(fileName)); @@ -126,10 +107,5 @@ namespace Squidex.Infrastructure.Assets return TaskHelper.Done; } - - private static string GetFileName(string id, long version, string suffix) - { - return StringExtensions.JoinNonEmpty("_", id, version.ToString(), suffix); - } } } diff --git a/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs b/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs index b48eb7269..85ccd58c9 100644 --- a/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs +++ b/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs @@ -14,27 +14,22 @@ namespace Squidex.Infrastructure.Assets { public sealed class NoopAssetStore : IAssetStore { - public string GeneratePublicUrl(string id, long version, string suffix) + public string GeneratePublicUrl(string fileName) { return null; } - public Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + public Task CopyAsync(string sourceFileName, string fileName, CancellationToken ct = default) { throw new NotSupportedException(); } - public Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + public Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) { throw new NotSupportedException(); } - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) - { - throw new NotSupportedException(); - } - - public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default) + public Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) { throw new NotSupportedException(); } @@ -43,10 +38,5 @@ namespace Squidex.Infrastructure.Assets { throw new NotSupportedException(); } - - public Task DeleteAsync(string id, long version, string suffix) - { - throw new NotSupportedException(); - } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index 52fd0067c..1d9ac1cb4 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs @@ -108,9 +108,11 @@ namespace Squidex.Domain.Apps.Entities.Assets private void AssertAssetHasBeenUploaded(long version, Guid commitId) { - A.CallTo(() => assetStore.UploadAsync(commitId.ToString(), stream, CancellationToken.None)) + var fileName = AssetStoreExtensions.GetFileName(assetId.ToString(), version); + + A.CallTo(() => assetStore.UploadAsync(commitId.ToString(), stream, false, CancellationToken.None)) .MustHaveHappened(); - A.CallTo(() => assetStore.CopyAsync(commitId.ToString(), assetId.ToString(), version, null, CancellationToken.None)) + A.CallTo(() => assetStore.CopyAsync(commitId.ToString(), fileName, CancellationToken.None)) .MustHaveHappened(); A.CallTo(() => assetStore.DeleteAsync(commitId.ToString())) .MustHaveHappened(); diff --git a/tests/Squidex.Infrastructure.Tests/Assets/AssetExtensionTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/AssetExtensionTests.cs new file mode 100644 index 000000000..c507e7529 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Assets/AssetExtensionTests.cs @@ -0,0 +1,112 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using FakeItEasy; +using Xunit; + +namespace Squidex.Infrastructure.Assets +{ + public class AssetExtensionTests + { + private readonly IAssetStore sut = A.Fake(); + private readonly Guid id = Guid.NewGuid(); + private readonly Stream stream = new MemoryStream(); + private readonly string fileName = Guid.NewGuid().ToString(); + + [Fact] + public void Should_copy_with_id_and_version() + { + sut.CopyAsync(fileName, id, 1, string.Empty); + + A.CallTo(() => sut.CopyAsync(fileName, $"{id}_1", default)) + .MustHaveHappened(); + } + + [Fact] + public void Should_copy_with_id_and_version_and_suffix() + { + sut.CopyAsync(fileName, id, 1, "Crop"); + + A.CallTo(() => sut.CopyAsync(fileName, $"{id}_1_Crop", default)) + .MustHaveHappened(); + } + + [Fact] + public void Should_upload_with_id_and_version() + { + sut.UploadAsync(id, 1, string.Empty, stream, true); + + A.CallTo(() => sut.UploadAsync($"{id}_1", stream, true, default)) + .MustHaveHappened(); + } + + [Fact] + public void Should_upload_with_id_and_version_and_suffix() + { + sut.UploadAsync(id, 1, "Crop", stream, true); + + A.CallTo(() => sut.UploadAsync($"{id}_1_Crop", stream, true, default)) + .MustHaveHappened(); + } + + [Fact] + public void Should_download_with_id_and_version() + { + sut.DownloadAsync(id, 1, string.Empty, stream); + + A.CallTo(() => sut.DownloadAsync($"{id}_1", stream, default)) + .MustHaveHappened(); + } + + [Fact] + public void Should_download_with_id_and_version_and_suffix() + { + sut.DownloadAsync(id, 1, "Crop", stream); + + A.CallTo(() => sut.DownloadAsync($"{id}_1_Crop", stream, default)) + .MustHaveHappened(); + } + + [Fact] + public void Should_delete_with_id_and_version() + { + sut.DeleteAsync(id, 1, string.Empty); + + A.CallTo(() => sut.DeleteAsync($"{id}_1")) + .MustHaveHappened(); + } + + [Fact] + public void Should_delete_with_id_and_version_and_suffix() + { + sut.DeleteAsync(id, 1, "Crop"); + + A.CallTo(() => sut.DeleteAsync($"{id}_1_Crop")) + .MustHaveHappened(); + } + + [Fact] + public void Should_generate_url_with_id_and_version() + { + sut.GeneratePublicUrl(id, 1, string.Empty); + + A.CallTo(() => sut.GeneratePublicUrl($"{id}_1")) + .MustHaveHappened(); + } + + [Fact] + public void Should_generate_url_with_id_and_version_and_suffix() + { + sut.GeneratePublicUrl(id, 1, "Crop"); + + A.CallTo(() => sut.GeneratePublicUrl($"{id}_1_Crop")) + .MustHaveHappened(); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs index 080a68d64..26dc836cb 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs @@ -15,8 +15,8 @@ namespace Squidex.Infrastructure.Assets public abstract class AssetStoreTests where T : IAssetStore { private readonly MemoryStream assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 }); - private readonly string assetId = Guid.NewGuid().ToString(); - private readonly string tempId = Guid.NewGuid().ToString(); + private readonly string fileName = Guid.NewGuid().ToString(); + private readonly string sourceFile = Guid.NewGuid().ToString(); private readonly Lazy sut; protected T Sut @@ -24,9 +24,9 @@ namespace Squidex.Infrastructure.Assets get { return sut.Value; } } - protected string AssetId + protected string FileName { - get { return assetId; } + get { return fileName; } } protected AssetStoreTests() @@ -39,93 +39,77 @@ namespace Squidex.Infrastructure.Assets [Fact] public virtual Task Should_throw_exception_if_asset_to_download_is_not_found() { - return Assert.ThrowsAsync(() => Sut.DownloadAsync(assetId, 1, "suffix", new MemoryStream())); + return Assert.ThrowsAsync(() => Sut.DownloadAsync(fileName, new MemoryStream())); } [Fact] public Task Should_throw_exception_if_asset_to_copy_is_not_found() { - return Assert.ThrowsAsync(() => Sut.CopyAsync(tempId, assetId, 1, null)); + return Assert.ThrowsAsync(() => Sut.CopyAsync(fileName, sourceFile)); } [Fact] - public async Task Should_read_and_write_file() + public async Task Should_write_and_read_file() { - await Sut.UploadAsync(assetId, 1, "suffix", assetData); + await Sut.UploadAsync(fileName, assetData); var readData = new MemoryStream(); - await Sut.DownloadAsync(assetId, 1, "suffix", readData); + await Sut.DownloadAsync(fileName, readData); Assert.Equal(assetData.ToArray(), readData.ToArray()); } [Fact] - public async Task Should_read_and_override_file() + public async Task Should_write_and_read_file_and_overwrite_non_existing() { - await Sut.UploadAsync(assetId, 1, "suffix", new MemoryStream(new byte[] { 0x3, 0x4, 0x5, 0x6 })); - await Sut.UploadAsync(assetId, 1, "suffix", assetData, true); + await Sut.UploadAsync(fileName, assetData, true); var readData = new MemoryStream(); - await Sut.DownloadAsync(assetId, 1, "suffix", readData); + await Sut.DownloadAsync(fileName, readData); Assert.Equal(assetData.ToArray(), readData.ToArray()); } [Fact] - public async Task Should_throw_exception_when_file_to_write_already_exists() + public async Task Should_write_and_read_overriding_file() { - await Sut.UploadAsync(assetId, 1, "suffix", assetData); - - await Assert.ThrowsAsync(() => Sut.UploadAsync(assetId, 1, "suffix", assetData)); - } + var oldData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 }); - [Fact] - public virtual async Task Should_read_and_write_temporary_file() - { - await Sut.UploadAsync(tempId, assetData); - await Sut.CopyAsync(tempId, assetId, 1, "suffix"); + await Sut.UploadAsync(fileName, oldData); + await Sut.UploadAsync(fileName, assetData, true); var readData = new MemoryStream(); - await Sut.DownloadAsync(assetId, 1, "suffix", readData); + await Sut.DownloadAsync(fileName, readData); Assert.Equal(assetData.ToArray(), readData.ToArray()); } [Fact] - public async Task Should_throw_exception_when_temporary_file_to_write_already_exists() + public async Task Should_throw_exception_when_file_to_write_already_exists() { - await Sut.UploadAsync(tempId, assetData); - await Sut.CopyAsync(tempId, assetId, 1, "suffix"); + await Sut.UploadAsync(fileName, assetData); - await Assert.ThrowsAsync(() => Sut.UploadAsync(tempId, assetData)); + await Assert.ThrowsAsync(() => Sut.UploadAsync(fileName, assetData)); } [Fact] public async Task Should_throw_exception_when_target_file_to_copy_to_already_exists() { - await Sut.UploadAsync(tempId, assetData); - await Sut.CopyAsync(tempId, assetId, 1, "suffix"); - - await Assert.ThrowsAsync(() => Sut.CopyAsync(tempId, assetId, 1, "suffix")); - } + await Sut.UploadAsync(sourceFile, assetData); + await Sut.CopyAsync(sourceFile, fileName); - [Fact] - public async Task Should_ignore_when_deleting_twice_by_name() - { - await Sut.UploadAsync(tempId, assetData); - await Sut.DeleteAsync(tempId); - await Sut.DeleteAsync(tempId); + await Assert.ThrowsAsync(() => Sut.CopyAsync(sourceFile, fileName)); } [Fact] - public async Task Should_ignore_when_deleting_twice_by_id() + public async Task Should_ignore_when_deleting_not_existing_file() { - await Sut.UploadAsync(tempId, 0, null, assetData); - await Sut.DeleteAsync(tempId, 0, null); - await Sut.DeleteAsync(tempId, 0, null); + await Sut.UploadAsync(sourceFile, assetData); + await Sut.DeleteAsync(sourceFile); + await Sut.DeleteAsync(sourceFile); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs index 6177410e2..f88e911f5 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs @@ -27,9 +27,9 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_calculate_source_url() { - var url = Sut.GeneratePublicUrl(AssetId, 1, null); + var url = Sut.GeneratePublicUrl(FileName); - Assert.Equal($"http://127.0.0.1:10000/devstoreaccount1/squidex-test-container/{AssetId}_1", url); + Assert.Equal($"http://127.0.0.1:10000/devstoreaccount1/squidex-test-container/{FileName}", url); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs index 0ec26d0c4..100d578f3 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs @@ -42,7 +42,7 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_calculate_source_url() { - var url = Sut.GeneratePublicUrl(AssetId, 1, null); + var url = Sut.GeneratePublicUrl(FileName); Assert.Null(url); } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs index b288961e1..72f0dcabe 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs @@ -27,7 +27,7 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_calculate_source_url() { - var url = Sut.GeneratePublicUrl(AssetId, 1, null); + var url = Sut.GeneratePublicUrl(FileName); Assert.Null(url); } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs index 7c193ca86..89fd74d70 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs @@ -19,7 +19,7 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_not_calculate_source_url() { - var url = Sut.GeneratePublicUrl(AssetId, 1, null); + var url = Sut.GeneratePublicUrl(FileName); Assert.Null(url); } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs index 2662803ab..191fa7164 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs @@ -27,7 +27,7 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_not_calculate_source_url() { - var url = Sut.GeneratePublicUrl(AssetId, 1, null); + var url = Sut.GeneratePublicUrl(FileName); Assert.Null(url); }