From 77892b9caf1b2122c94525228bc1b686bf349c6f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 23 Jul 2018 18:50:26 +0200 Subject: [PATCH] Full support of AssetAlreadyExistsException --- .../Assets/AzureBlobAssetStore.cs | 71 +++++++++++-------- .../Assets/GoogleCloudAssetStore.cs | 66 +++++++++-------- .../Assets/MongoGridFsAssetStore.cs | 14 ++-- .../Assets/FolderAssetStore.cs | 8 +-- .../Assets/AssetStoreTests.cs | 5 ++ .../Assets/AzureBlobAssetStoreTests.cs | 7 +- .../Assets/FolderAssetStoreTests.cs | 8 +-- .../Assets/GoogleCloudAssetStoreTests.cs | 7 +- .../Assets/MongoGridFsAssetStoreTests.cs | 5 +- 9 files changed, 101 insertions(+), 90 deletions(-) diff --git a/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs b/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs index 6daa79886..628ebdf20 100644 --- a/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs +++ b/src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs @@ -57,44 +57,47 @@ namespace Squidex.Infrastructure.Assets return new Uri(blobContainer.StorageUri.PrimaryUri, $"/{containerName}/{blobName}").ToString(); } - public async Task CopyAsync(string name, string id, long version, string suffix, CancellationToken ct = default(CancellationToken)) + public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default(CancellationToken)) { - var blobName = GetObjectName(id, version, suffix); - var blobRef = blobContainer.GetBlobReference(blobName); + var targetName = GetObjectName(id, version, suffix); + var targetBlob = blobContainer.GetBlobReference(targetName); - var tempBlob = blobContainer.GetBlockBlobReference(name); + var sourceBlob = blobContainer.GetBlockBlobReference(sourceFileName); try { - await blobRef.StartCopyAsync(tempBlob.Uri, null, null, null, null, ct); + await targetBlob.StartCopyAsync(sourceBlob.Uri, null, AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); - while (blobRef.CopyState.Status == CopyStatus.Pending) + while (targetBlob.CopyState.Status == CopyStatus.Pending) { ct.ThrowIfCancellationRequested(); await Task.Delay(50); - await blobRef.FetchAttributesAsync(null, null, null, ct); + await targetBlob.FetchAttributesAsync(null, null, null, ct); } - if (blobRef.CopyState.Status != CopyStatus.Success) + if (targetBlob.CopyState.Status != CopyStatus.Success) { - throw new StorageException($"Copy of temporary file failed: {blobRef.CopyState.Status}"); + throw new StorageException($"Copy of temporary file failed: {targetBlob.CopyState.Status}"); } } + catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) + { + throw new AssetAlreadyExistsException(targetName); + } catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) { - throw new AssetNotFoundException(name, ex); + throw new AssetNotFoundException(sourceFileName, ex); } } public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) { - var blobName = GetObjectName(id, version, suffix); - var blobRef = blobContainer.GetBlockBlobReference(blobName); + var blob = blobContainer.GetBlockBlobReference(GetObjectName(id, version, suffix)); try { - await blobRef.DownloadToStreamAsync(stream, null, null, null, ct); + await blob.DownloadToStreamAsync(stream, null, null, null, ct); } catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) { @@ -102,37 +105,45 @@ namespace Squidex.Infrastructure.Assets } } - public async Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) + public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) { - var blobName = GetObjectName(id, version, suffix); - var blobRef = blobContainer.GetBlockBlobReference(blobName); - - blobRef.Metadata[AssetVersion] = version.ToString(); - blobRef.Metadata[AssetId] = id; + return UploadCoreAsync(GetObjectName(id, version, suffix), stream, ct); + } - await blobRef.UploadFromStreamAsync(stream, null, null, null, ct); - await blobRef.SetMetadataAsync(); + public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default(CancellationToken)) + { + return UploadCoreAsync(fileName, stream, ct); } - public Task UploadAsync(string name, Stream stream, CancellationToken ct = default(CancellationToken)) + public Task DeleteAsync(string id, long version, string suffix) { - var tempBlob = blobContainer.GetBlockBlobReference(name); + return DeleteCoreAsync(GetObjectName(id, version, suffix)); + } - return tempBlob.UploadFromStreamAsync(stream, null, null, null, ct); + public Task DeleteAsync(string fileName) + { + return DeleteCoreAsync(fileName); } - public Task DeleteAsync(string name) + private Task DeleteCoreAsync(string blobName) { - var tempBlob = blobContainer.GetBlockBlobReference(name); + var blob = blobContainer.GetBlockBlobReference(blobName); - return tempBlob.DeleteIfExistsAsync(); + return blob.DeleteIfExistsAsync(); } - public Task DeleteAsync(string id, long version, string suffix) + private async Task UploadCoreAsync(string blobName, Stream stream, CancellationToken ct) { - var tempBlob = blobContainer.GetBlockBlobReference(GetObjectName(id, version, suffix)); + try + { + var tempBlob = blobContainer.GetBlockBlobReference(blobName); - return tempBlob.DeleteIfExistsAsync(); + await tempBlob.UploadFromStreamAsync(stream, AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); + } + catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) + { + throw new AssetAlreadyExistsException(blobName); + } } private string GetObjectName(string id, long version, string suffix) diff --git a/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs b/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs index 7251d070a..9a8a31e7c 100644 --- a/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs +++ b/src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs @@ -18,6 +18,8 @@ namespace Squidex.Infrastructure.Assets { public sealed class GoogleCloudAssetStore : IAssetStore, IInitializable { + private static readonly UploadObjectOptions IfNotExists = new UploadObjectOptions { IfGenerationMatch = 0 }; + private static readonly CopyObjectOptions IfNotExistsCopy = new CopyObjectOptions { IfGenerationMatch = 0 }; private readonly string bucketName; private StorageClient storageClient; @@ -49,29 +51,21 @@ namespace Squidex.Infrastructure.Assets return $"https://storage.cloud.google.com/{bucketName}/{objectName}"; } - public Task UploadAsync(string name, Stream stream, CancellationToken ct = default(CancellationToken)) - { - return storageClient.UploadObjectAsync(bucketName, name, "application/octet-stream", stream, cancellationToken: ct); - } - - public async Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) - { - var objectName = GetObjectName(id, version, suffix); - - await storageClient.UploadObjectAsync(bucketName, objectName, "application/octet-stream", stream, cancellationToken: ct); - } - - public async Task CopyAsync(string name, string id, long version, string suffix, CancellationToken ct = default(CancellationToken)) + public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default(CancellationToken)) { var objectName = GetObjectName(id, version, suffix); try { - await storageClient.CopyObjectAsync(bucketName, name, bucketName, objectName, cancellationToken: ct); + await storageClient.CopyObjectAsync(bucketName, sourceFileName, bucketName, objectName, IfNotExistsCopy, ct); } catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound) { - throw new AssetNotFoundException(name, ex); + throw new AssetNotFoundException(sourceFileName, ex); + } + catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.PreconditionFailed) + { + throw new AssetAlreadyExistsException(objectName); } } @@ -89,33 +83,47 @@ namespace Squidex.Infrastructure.Assets } } - public async Task DeleteAsync(string name) + public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) + { + return UploadCoreAsync(GetObjectName(id, version, suffix), stream, ct); + } + + public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default(CancellationToken)) + { + return UploadCoreAsync(fileName, stream, 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 async Task UploadCoreAsync(string objectName, Stream stream, CancellationToken ct) { try { - await storageClient.DeleteObjectAsync(bucketName, name); + await storageClient.UploadObjectAsync(bucketName, objectName, "application/octet-stream", stream, IfNotExists, ct); } - catch (GoogleApiException ex) + catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.PreconditionFailed) { - if (ex.HttpStatusCode != HttpStatusCode.NotFound) - { - throw; - } + throw new AssetAlreadyExistsException(objectName); } } - public async Task DeleteAsync(string id, long version, string suffix) + private async Task DeleteCoreAsync(string objectName) { try { - await storageClient.DeleteObjectAsync(bucketName, GetObjectName(id, version, suffix)); + await storageClient.DeleteObjectAsync(bucketName, objectName); } - catch (GoogleApiException ex) + catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound) { - if (ex.HttpStatusCode != HttpStatusCode.NotFound) - { - throw; - } + return; } } diff --git a/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs b/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs index dbc093b37..faadf950a 100644 --- a/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs @@ -78,19 +78,14 @@ namespace Squidex.Infrastructure.Assets } } - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default(CancellationToken)) - { - return UploadFileCoreAsync(fileName, stream, ct); - } - public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) { return UploadFileCoreAsync(GetFileName(id, version, suffix), stream, ct); } - public Task DeleteAsync(string fileName) + public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default(CancellationToken)) { - return DeleteCoreAsync(fileName); + return UploadFileCoreAsync(fileName, stream, ct); } public Task DeleteAsync(string id, long version, string suffix) @@ -98,6 +93,11 @@ namespace Squidex.Infrastructure.Assets return DeleteCoreAsync(GetFileName(id, version, suffix)); } + public Task DeleteAsync(string fileName) + { + return DeleteCoreAsync(fileName); + } + private async Task DeleteCoreAsync(string id) { try diff --git a/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs b/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs index 9e3772530..8bcda3ac6 100644 --- a/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs +++ b/src/Squidex.Infrastructure/Assets/FolderAssetStore.cs @@ -98,14 +98,14 @@ namespace Squidex.Infrastructure.Assets } } - public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default(CancellationToken)) + public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) { - return UploadCoreAsync(GetFile(fileName), stream, ct); + return UploadCoreAsync(GetFile(id, version, suffix), stream, ct); } - public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default(CancellationToken)) + public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default(CancellationToken)) { - return UploadCoreAsync(GetFile(id, version, suffix), stream, ct); + return UploadCoreAsync(GetFile(fileName), stream, ct); } public Task DeleteAsync(string id, long version, string suffix) diff --git a/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs index e384b4b34..4d9f8927b 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs @@ -31,6 +31,11 @@ namespace Squidex.Infrastructure.Assets get { return sut.Value; } } + protected string AssetId + { + get { return assetId; } + } + public abstract T CreateStore(); public abstract void Dispose(); diff --git a/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs index 9f1f47f96..0ce5b5820 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using Xunit; #pragma warning disable xUnit1000 // Test classes must be public @@ -26,11 +25,9 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_calculate_source_url() { - Sut.Initialize(); + var url = Sut.GenerateSourceUrl(AssetId, 1, null); - var id = Guid.NewGuid().ToString(); - - Assert.Equal($"http://127.0.0.1:10000/squidex-test-container/{id}_1", Sut.GenerateSourceUrl(id, 1, null)); + Assert.Equal($"http://127.0.0.1:10000/squidex-test-container/{AssetId}_1", url); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs index 8885d1225..a1d221382 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs @@ -39,19 +39,15 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_create_directory_when_connecting() { - Sut.Initialize(); - Assert.True(Directory.Exists(testFolder)); } [Fact] public void Should_calculate_source_url() { - Sut.Initialize(); - - var id = Guid.NewGuid().ToString(); + var url = Sut.GenerateSourceUrl(AssetId, 1, null); - Assert.Equal(Path.Combine(testFolder, $"{id}_1"), Sut.GenerateSourceUrl(id, 1, null)); + Assert.Equal(Path.Combine(testFolder, $"{AssetId}_1"), url); } private static string CreateInvalidPath() diff --git a/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs index 858fb813c..d31420bcf 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using Xunit; #pragma warning disable xUnit1000 // Test classes must be public @@ -26,11 +25,9 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_calculate_source_url() { - Sut.Initialize(); + var url = Sut.GenerateSourceUrl(AssetId, 1, null); - var id = Guid.NewGuid().ToString(); - - Assert.Equal($"https://storage.cloud.google.com/squidex-test/{id}_1", Sut.GenerateSourceUrl(id, 1, null)); + Assert.Equal($"https://storage.cloud.google.com/squidex-test/{AssetId}_1", url); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs index eb1fce5f4..eb101e3ef 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using MongoDB.Driver; using MongoDB.Driver.GridFS; using Xunit; @@ -43,9 +42,7 @@ namespace Squidex.Infrastructure.Assets [Fact] public void Should_not_calculate_source_url() { - Sut.Initialize(); - - Assert.Equal("UNSUPPORTED", Sut.GenerateSourceUrl(Guid.NewGuid().ToString(), 1, null)); + Assert.Equal("UNSUPPORTED", Sut.GenerateSourceUrl(AssetId, 1, null)); } } } \ No newline at end of file