Browse Source

Asset improvements.

pull/352/head
Sebastian 7 years ago
parent
commit
915e5c7956
  1. 85
      src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
  2. 70
      src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
  3. 74
      src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs
  4. 74
      src/Squidex.Infrastructure/Assets/AssetStoreExtensions.cs
  5. 84
      src/Squidex.Infrastructure/Assets/FolderAssetStore.cs
  6. 12
      src/Squidex.Infrastructure/Assets/IAssetStore.cs
  7. 40
      src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs
  8. 18
      src/Squidex.Infrastructure/Assets/NoopAssetStore.cs
  9. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs
  10. 112
      tests/Squidex.Infrastructure.Tests/Assets/AssetExtensionTests.cs
  11. 70
      tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs
  12. 4
      tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs
  13. 2
      tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs
  14. 2
      tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs
  15. 2
      tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs
  16. 2
      tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs

85
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();
}
}
}

70
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);
}
}
}

74
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<BsonDocument> 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<BsonDocument> 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;
}
}
}

74
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);
}
}
}

84
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));
}
}
}

12
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);
}
}

40
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);
}
}
}

18
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();
}
}
}

6
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();

112
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<IAssetStore>();
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();
}
}
}

70
tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs

@ -15,8 +15,8 @@ namespace Squidex.Infrastructure.Assets
public abstract class AssetStoreTests<T> 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<T> 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<AssetNotFoundException>(() => Sut.DownloadAsync(assetId, 1, "suffix", new MemoryStream()));
return Assert.ThrowsAsync<AssetNotFoundException>(() => Sut.DownloadAsync(fileName, new MemoryStream()));
}
[Fact]
public Task Should_throw_exception_if_asset_to_copy_is_not_found()
{
return Assert.ThrowsAsync<AssetNotFoundException>(() => Sut.CopyAsync(tempId, assetId, 1, null));
return Assert.ThrowsAsync<AssetNotFoundException>(() => 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<AssetAlreadyExistsException>(() => 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<AssetAlreadyExistsException>(() => Sut.UploadAsync(tempId, assetData));
await Assert.ThrowsAsync<AssetAlreadyExistsException>(() => 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<AssetAlreadyExistsException>(() => 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<AssetAlreadyExistsException>(() => 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);
}
}
}

4
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);
}
}
}

2
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);
}

2
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);
}

2
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);
}

2
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);
}

Loading…
Cancel
Save