Browse Source

Asset improvements.

pull/352/head
Sebastian 8 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) if (blobContainer.Properties.PublicAccess != BlobContainerPublicAccessType.Blob)
{ {
var sourceName = GetObjectName(id, version, suffix); var blob = blobContainer.GetBlockBlobReference(fileName);
var sourceBlob = blobContainer.GetBlockBlobReference(sourceName);
return sourceBlob.Uri.ToString(); return blob.Uri.ToString();
} }
return null; 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); Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName));
var targetBlob = blobContainer.GetBlobReference(targetName); Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName));
var sourceBlob = blobContainer.GetBlockBlobReference(sourceFileName);
try try
{ {
var sourceBlob = blobContainer.GetBlockBlobReference(sourceFileName);
var targetBlob = blobContainer.GetBlobReference(targetFileName);
await targetBlob.StartCopyAsync(sourceBlob.Uri, null, AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); await targetBlob.StartCopyAsync(sourceBlob.Uri, null, AccessCondition.GenerateIfNotExistsCondition(), null, null, ct);
while (targetBlob.CopyState.Status == CopyStatus.Pending) while (targetBlob.CopyState.Status == CopyStatus.Pending)
@ -87,7 +90,7 @@ namespace Squidex.Infrastructure.Assets
} }
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409)
{ {
throw new AssetAlreadyExistsException(targetName); throw new AssetAlreadyExistsException(targetFileName);
} }
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) 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); Guard.NotNullOrEmpty(fileName, nameof(fileName));
var sourceBlob = blobContainer.GetBlockBlobReference(sourceName);
try 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) 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) 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);
}
private Task DeleteCoreAsync(string blobName)
{ {
var blob = blobContainer.GetBlockBlobReference(blobName); Guard.NotNullOrEmpty(fileName, nameof(fileName));
return blob.DeleteIfExistsAsync();
}
private async Task UploadCoreAsync(string blobName, Stream stream, bool overwrite = false, CancellationToken ct = default)
{
try try
{ {
var tempBlob = blobContainer.GetBlockBlobReference(blobName); var tempBlob = blobContainer.GetBlockBlobReference(fileName);
await tempBlob.UploadFromStreamAsync(stream, overwrite ? null : AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); await tempBlob.UploadFromStreamAsync(stream, overwrite ? null : AccessCondition.GenerateIfNotExistsCondition(), null, null, ct);
} }
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) 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) var blob = blobContainer.GetBlockBlobReference(fileName);
{
throw new InvalidOperationException("No connection established yet.");
}
var name = $"{id}_{version}"; return blob.DeleteIfExistsAsync();
if (!string.IsNullOrWhiteSpace(suffix))
{
name += "_" + suffix;
}
return name;
} }
} }
} }

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; 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 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) catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound)
{ {
@ -62,85 +63,50 @@ namespace Squidex.Infrastructure.Assets
} }
catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.PreconditionFailed) 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 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) 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); Guard.NotNullOrEmpty(fileName, nameof(fileName));
}
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 async Task UploadCoreAsync(string objectName, Stream stream, bool overwrite = false, CancellationToken ct = default)
{
try 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) 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 try
{ {
await storageClient.DeleteObjectAsync(bucketName, objectName); await storageClient.DeleteObjectAsync(bucketName, fileName);
} }
catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound) catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound)
{ {
return; 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; 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 try
{ {
var target = GetFileName(id, version, suffix); var sourceName = GetFileName(sourceFileName, nameof(sourceFileName));
using (var readStream = await bucket.OpenDownloadStreamAsync(sourceFileName, cancellationToken: ct)) using (var readStream = await bucket.OpenDownloadStreamAsync(sourceFileName, cancellationToken: ct))
{ {
await UploadFileCoreAsync(target, readStream, false, ct); await UploadAsync(targetFileName, readStream, false, ct);
} }
} }
catch (GridFSFileNotFoundException ex) 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 try
{ {
var name = GetFileName(id, version, suffix); var name = GetFileName(fileName, nameof(fileName));
using (var readStream = await bucket.OpenDownloadStreamAsync(name, cancellationToken: ct)) using (var readStream = await bucket.OpenDownloadStreamAsync(name, cancellationToken: ct))
{ {
@ -74,66 +74,52 @@ namespace Squidex.Infrastructure.Assets
} }
catch (GridFSFileNotFoundException ex) 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); try
} {
var name = GetFileName(fileName, nameof(fileName));
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));
}
public Task DeleteAsync(string fileName) if (overwrite)
{ {
return DeleteCoreAsync(fileName); await DeleteAsync(fileName);
} }
private async Task DeleteCoreAsync(string id) await bucket.UploadFromStreamAsync(fileName, fileName, stream, cancellationToken: ct);
{ }
try 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 try
{ {
if (overwrite) var name = GetFileName(fileName, nameof(fileName));
{
await bucket.DeleteAsync(id, ct);
}
await bucket.UploadFromStreamAsync(id, id, stream, cancellationToken: ct); await bucket.DeleteAsync(name);
} }
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) catch (GridFSFileNotFoundException)
{
throw new AssetAlreadyExistsException(id);
}
catch (MongoBulkWriteException<BsonDocument> ex) when (ex.WriteErrors.Any(x => x.Category == ServerErrorCategory.DuplicateKey))
{ {
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; 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 var targetFile = GetFile(targetFileName);
{ var sourceFile = GetFile(sourceFileName);
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);
try try
{ {
var file = GetFile(sourceFileName); sourceFile.CopyTo(targetFile.FullName);
file.CopyTo(targetFile.FullName);
return TaskHelper.Done; return TaskHelper.Done;
} }
catch (IOException) when (targetFile.Exists) catch (IOException) when (targetFile.Exists)
{ {
throw new AssetAlreadyExistsException(targetFile.Name); throw new AssetAlreadyExistsException(targetFileName);
} }
catch (FileNotFoundException ex) 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) public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default)
{
return UploadCoreAsync(GetFile(id, version, suffix), stream, overwrite, ct);
}
public Task UploadAsync(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) var file = GetFile(fileName);
{
return DeleteFileCoreAsync(GetFile(id, version, suffix));
}
public Task DeleteAsync(string fileName) try
{ {
return DeleteFileCoreAsync(GetFile(fileName)); 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 try
{ {
using (var fileStream = file.Open(overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.Write)) 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) private FileInfo GetFile(string fileName)
@ -155,10 +140,5 @@ namespace Squidex.Infrastructure.Assets
{ {
return Path.Combine(directory.FullName, name); 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 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 fileName, Stream stream, bool overwrite = false, CancellationToken ct = default);
Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default);
Task DeleteAsync(string fileName); 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 readerLock = new AsyncLock();
private readonly AsyncLock writerLock = new AsyncLock(); private readonly AsyncLock writerLock = new AsyncLock();
public string GeneratePublicUrl(string id, long version, string suffix) public string GeneratePublicUrl(string fileName)
{ {
return null; 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(sourceFileName, nameof(sourceFileName));
Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName));
if (!streams.TryGetValue(sourceFileName, out var sourceStream)) if (!streams.TryGetValue(sourceFileName, out var sourceStream))
{ {
@ -36,15 +36,13 @@ namespace Squidex.Infrastructure.Assets
using (await readerLock.LockAsync()) 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)); Guard.NotNullOrEmpty(fileName, nameof(fileName));
var fileName = GetFileName(id, version, suffix);
if (!streams.TryGetValue(fileName, out var sourceStream)) 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) public async Task UploadAsync(string fileName, 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)
{ {
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(); var memoryStream = new MemoryStream();
async Task CopyAsync() 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) public Task DeleteAsync(string fileName)
{ {
Guard.NotNullOrEmpty(fileName, nameof(fileName)); Guard.NotNullOrEmpty(fileName, nameof(fileName));
@ -126,10 +107,5 @@ namespace Squidex.Infrastructure.Assets
return TaskHelper.Done; 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 sealed class NoopAssetStore : IAssetStore
{ {
public string GeneratePublicUrl(string id, long version, string suffix) public string GeneratePublicUrl(string fileName)
{ {
return null; 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(); 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(); throw new NotSupportedException();
} }
public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) public Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default)
{
throw new NotSupportedException();
}
public Task UploadAsync(string id, long version, string suffix, Stream stream, bool overwrite = false, CancellationToken ct = default)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
@ -43,10 +38,5 @@ namespace Squidex.Infrastructure.Assets
{ {
throw new NotSupportedException(); 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) 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(); .MustHaveHappened();
A.CallTo(() => assetStore.CopyAsync(commitId.ToString(), assetId.ToString(), version, null, CancellationToken.None)) A.CallTo(() => assetStore.CopyAsync(commitId.ToString(), fileName, CancellationToken.None))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetStore.DeleteAsync(commitId.ToString())) A.CallTo(() => assetStore.DeleteAsync(commitId.ToString()))
.MustHaveHappened(); .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 public abstract class AssetStoreTests<T> where T : IAssetStore
{ {
private readonly MemoryStream assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 }); private readonly MemoryStream assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 });
private readonly string assetId = Guid.NewGuid().ToString(); private readonly string fileName = Guid.NewGuid().ToString();
private readonly string tempId = Guid.NewGuid().ToString(); private readonly string sourceFile = Guid.NewGuid().ToString();
private readonly Lazy<T> sut; private readonly Lazy<T> sut;
protected T Sut protected T Sut
@ -24,9 +24,9 @@ namespace Squidex.Infrastructure.Assets
get { return sut.Value; } get { return sut.Value; }
} }
protected string AssetId protected string FileName
{ {
get { return assetId; } get { return fileName; }
} }
protected AssetStoreTests() protected AssetStoreTests()
@ -39,93 +39,77 @@ namespace Squidex.Infrastructure.Assets
[Fact] [Fact]
public virtual Task Should_throw_exception_if_asset_to_download_is_not_found() 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] [Fact]
public Task Should_throw_exception_if_asset_to_copy_is_not_found() 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] [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(); var readData = new MemoryStream();
await Sut.DownloadAsync(assetId, 1, "suffix", readData); await Sut.DownloadAsync(fileName, readData);
Assert.Equal(assetData.ToArray(), readData.ToArray()); Assert.Equal(assetData.ToArray(), readData.ToArray());
} }
[Fact] [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(fileName, assetData, true);
await Sut.UploadAsync(assetId, 1, "suffix", assetData, true);
var readData = new MemoryStream(); var readData = new MemoryStream();
await Sut.DownloadAsync(assetId, 1, "suffix", readData); await Sut.DownloadAsync(fileName, readData);
Assert.Equal(assetData.ToArray(), readData.ToArray()); Assert.Equal(assetData.ToArray(), readData.ToArray());
} }
[Fact] [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); var oldData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 });
await Assert.ThrowsAsync<AssetAlreadyExistsException>(() => Sut.UploadAsync(assetId, 1, "suffix", assetData));
}
[Fact] await Sut.UploadAsync(fileName, oldData);
public virtual async Task Should_read_and_write_temporary_file() await Sut.UploadAsync(fileName, assetData, true);
{
await Sut.UploadAsync(tempId, assetData);
await Sut.CopyAsync(tempId, assetId, 1, "suffix");
var readData = new MemoryStream(); var readData = new MemoryStream();
await Sut.DownloadAsync(assetId, 1, "suffix", readData); await Sut.DownloadAsync(fileName, readData);
Assert.Equal(assetData.ToArray(), readData.ToArray()); Assert.Equal(assetData.ToArray(), readData.ToArray());
} }
[Fact] [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.UploadAsync(fileName, assetData);
await Sut.CopyAsync(tempId, assetId, 1, "suffix");
await Assert.ThrowsAsync<AssetAlreadyExistsException>(() => Sut.UploadAsync(tempId, assetData)); await Assert.ThrowsAsync<AssetAlreadyExistsException>(() => Sut.UploadAsync(fileName, assetData));
} }
[Fact] [Fact]
public async Task Should_throw_exception_when_target_file_to_copy_to_already_exists() public async Task Should_throw_exception_when_target_file_to_copy_to_already_exists()
{ {
await Sut.UploadAsync(tempId, assetData); await Sut.UploadAsync(sourceFile, assetData);
await Sut.CopyAsync(tempId, assetId, 1, "suffix"); await Sut.CopyAsync(sourceFile, fileName);
await Assert.ThrowsAsync<AssetAlreadyExistsException>(() => Sut.CopyAsync(tempId, assetId, 1, "suffix"));
}
[Fact] await Assert.ThrowsAsync<AssetAlreadyExistsException>(() => Sut.CopyAsync(sourceFile, fileName));
public async Task Should_ignore_when_deleting_twice_by_name()
{
await Sut.UploadAsync(tempId, assetData);
await Sut.DeleteAsync(tempId);
await Sut.DeleteAsync(tempId);
} }
[Fact] [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.UploadAsync(sourceFile, assetData);
await Sut.DeleteAsync(tempId, 0, null); await Sut.DeleteAsync(sourceFile);
await Sut.DeleteAsync(tempId, 0, null); await Sut.DeleteAsync(sourceFile);
} }
} }
} }

4
tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs

@ -27,9 +27,9 @@ namespace Squidex.Infrastructure.Assets
[Fact] [Fact]
public void Should_calculate_source_url() 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] [Fact]
public void Should_calculate_source_url() public void Should_calculate_source_url()
{ {
var url = Sut.GeneratePublicUrl(AssetId, 1, null); var url = Sut.GeneratePublicUrl(FileName);
Assert.Null(url); Assert.Null(url);
} }

2
tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs

@ -27,7 +27,7 @@ namespace Squidex.Infrastructure.Assets
[Fact] [Fact]
public void Should_calculate_source_url() public void Should_calculate_source_url()
{ {
var url = Sut.GeneratePublicUrl(AssetId, 1, null); var url = Sut.GeneratePublicUrl(FileName);
Assert.Null(url); Assert.Null(url);
} }

2
tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs

@ -19,7 +19,7 @@ namespace Squidex.Infrastructure.Assets
[Fact] [Fact]
public void Should_not_calculate_source_url() public void Should_not_calculate_source_url()
{ {
var url = Sut.GeneratePublicUrl(AssetId, 1, null); var url = Sut.GeneratePublicUrl(FileName);
Assert.Null(url); Assert.Null(url);
} }

2
tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs

@ -27,7 +27,7 @@ namespace Squidex.Infrastructure.Assets
[Fact] [Fact]
public void Should_not_calculate_source_url() public void Should_not_calculate_source_url()
{ {
var url = Sut.GeneratePublicUrl(AssetId, 1, null); var url = Sut.GeneratePublicUrl(FileName);
Assert.Null(url); Assert.Null(url);
} }

Loading…
Cancel
Save