diff --git a/Squidex.sln b/Squidex.sln index 6d652542f..e9cfa76b7 100644 --- a/Squidex.sln +++ b/Squidex.sln @@ -58,7 +58,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users.Tests" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.GetEventStore", "src\Squidex.Infrastructure.GetEventStore\Squidex.Infrastructure.GetEventStore.csproj", "{EF75E488-1324-4E18-A1BD-D3A05AE67B1F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Infrastructure.AzureStorage", "src\Squidex.Infrastructure.AzureStorage\Squidex.Infrastructure.AzureStorage.csproj", "{7931187E-A1E6-4F89-8BC8-20A1E445579F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Infrastructure.Azure", "src\Squidex.Infrastructure.Azure\Squidex.Infrastructure.Azure.csproj", "{7931187E-A1E6-4F89-8BC8-20A1E445579F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Squidex.Infrastructure.AzureStorage/Squidex.Infrastructure.AzureStorage.csproj b/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj similarity index 100% rename from src/Squidex.Infrastructure.AzureStorage/Squidex.Infrastructure.AzureStorage.csproj rename to src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj diff --git a/src/Squidex.Infrastructure.Azure/Storage/AzureBlobAssetStore.cs b/src/Squidex.Infrastructure.Azure/Storage/AzureBlobAssetStore.cs new file mode 100644 index 000000000..0fa3eec15 --- /dev/null +++ b/src/Squidex.Infrastructure.Azure/Storage/AzureBlobAssetStore.cs @@ -0,0 +1,90 @@ +// ========================================================================== +// AzureBlobAssetStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage.Blob; +using Squidex.Infrastructure.Assets; + +namespace Squidex.Infrastructure.Azure.Storage +{ + public class AzureBlobAssetStore : IAssetStore, IExternalSystem + { + private readonly IStorageAccountManager azureStorageAccount; + private readonly string containerName; + private CloudBlobContainer blobContainer; + private const string AssetVersion = "AssetVersion"; + private const string AssetId = "AssetId"; + + public AzureBlobAssetStore(IStorageAccountManager azureStorageAccount, string containerName) + { + Guard.NotNullOrEmpty(containerName, nameof(containerName)); + Guard.NotNull(azureStorageAccount, nameof(azureStorageAccount)); + + this.azureStorageAccount = azureStorageAccount; + this.containerName = containerName; + } + + public async Task DownloadAsync(string id, long version, string suffix, Stream stream) + { + var blobName = GetObjectName(id, version, suffix); + var blob = blobContainer.GetBlockBlobReference(blobName); + + if (!await blob.ExistsAsync()) + return; + + await blob.DownloadToStreamAsync(stream); + } + + public async Task UploadAsync(string id, long version, string suffix, Stream stream) + { + var blobName = GetObjectName(id, version, suffix); + var blob = blobContainer.GetBlockBlobReference(blobName); + + if (await blob.ExistsAsync()) + { + return; + } + + if (!blob.Metadata.ContainsKey(AssetVersion)) + blob.Metadata.Add(AssetVersion, version.ToString()); + else + blob.Metadata[AssetVersion] = version.ToString(); + + blob.Metadata[AssetId] = id; + + await blob.UploadFromStreamAsync(stream); + await blob.SetMetadataAsync(); + } + + public void Connect() + { + blobContainer = azureStorageAccount.GetContainer(containerName); + } + + private string GetObjectName(string id, long version, string suffix) + { + Guard.NotNullOrEmpty(id, nameof(id)); + + if (blobContainer == null) + { + throw new InvalidOperationException("No connection established yet."); + } + + var name = $"{id}_{version}"; + + if (!string.IsNullOrWhiteSpace(suffix)) + { + name += "_" + suffix; + } + + return name; + } + } +} diff --git a/src/Squidex.Infrastructure.AzureStorage/AzureStorageException.cs b/src/Squidex.Infrastructure.Azure/Storage/AzureStorageException.cs similarity index 94% rename from src/Squidex.Infrastructure.AzureStorage/AzureStorageException.cs rename to src/Squidex.Infrastructure.Azure/Storage/AzureStorageException.cs index 9121551cd..c3fdbe011 100644 --- a/src/Squidex.Infrastructure.AzureStorage/AzureStorageException.cs +++ b/src/Squidex.Infrastructure.Azure/Storage/AzureStorageException.cs @@ -9,7 +9,7 @@ using System; using Microsoft.WindowsAzure.Storage; -namespace Squidex.Infrastructure.AzureStorage +namespace Squidex.Infrastructure.Azure.Storage { [Serializable] public class AzureStorageException : StorageException diff --git a/src/Squidex.Infrastructure.AzureStorage/IStorageAccountManager.cs b/src/Squidex.Infrastructure.Azure/Storage/IStorageAccountManager.cs similarity index 80% rename from src/Squidex.Infrastructure.AzureStorage/IStorageAccountManager.cs rename to src/Squidex.Infrastructure.Azure/Storage/IStorageAccountManager.cs index f38c2d3c8..259ac8028 100644 --- a/src/Squidex.Infrastructure.AzureStorage/IStorageAccountManager.cs +++ b/src/Squidex.Infrastructure.Azure/Storage/IStorageAccountManager.cs @@ -6,14 +6,17 @@ // All rights reserved. // ========================================================================== +using System.Threading.Tasks; using Microsoft.WindowsAzure.Storage.Blob; -namespace Squidex.Infrastructure.AzureStorage +namespace Squidex.Infrastructure.Azure.Storage { public interface IStorageAccountManager { CloudBlobClient CreateCloudBlobClient(); string GetSharedAccessSignature(); + + CloudBlobContainer GetContainer(string name); } } diff --git a/src/Squidex.Infrastructure.AzureStorage/StorageAccountManager.cs b/src/Squidex.Infrastructure.Azure/Storage/StorageAccountManager.cs similarity index 86% rename from src/Squidex.Infrastructure.AzureStorage/StorageAccountManager.cs rename to src/Squidex.Infrastructure.Azure/Storage/StorageAccountManager.cs index 3de1822ce..11f56f9ae 100644 --- a/src/Squidex.Infrastructure.AzureStorage/StorageAccountManager.cs +++ b/src/Squidex.Infrastructure.Azure/Storage/StorageAccountManager.cs @@ -7,10 +7,11 @@ // ========================================================================== using System; +using System.Threading.Tasks; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; -namespace Squidex.Infrastructure.AzureStorage +namespace Squidex.Infrastructure.Azure.Storage { public class StorageAccountManager : IStorageAccountManager { @@ -43,5 +44,11 @@ namespace Squidex.Infrastructure.AzureStorage Permissions = SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.List }); } + + public CloudBlobContainer GetContainer(string name) + { + var blobClient = CreateCloudBlobClient(); + return blobClient.GetContainerReference(name); + } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure.AzureStorage/AzureBlobAssetStore.cs b/src/Squidex.Infrastructure.AzureStorage/AzureBlobAssetStore.cs deleted file mode 100644 index 5b3085186..000000000 --- a/src/Squidex.Infrastructure.AzureStorage/AzureBlobAssetStore.cs +++ /dev/null @@ -1,147 +0,0 @@ -// ========================================================================== -// AzureBlobAssetStore.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Blob; -using Squidex.Infrastructure.Assets; - -namespace Squidex.Infrastructure.AzureStorage -{ - public class AzureBlobAssetStore : IAssetStore, IExternalSystem - { - private readonly IBlobContainerProvider blobContainerProvider; - private readonly string containerName; - private CloudBlobContainer blobContainer; - private const string AssetVersion = "AssetVersion"; - private const string AssetId = "AssetId"; - - public AzureBlobAssetStore(IBlobContainerProvider blobContainerProvider, string containerName) - { - Guard.NotNullOrEmpty(containerName, nameof(containerName)); - Guard.NotNull(blobContainerProvider, nameof(blobContainerProvider)); - - this.blobContainerProvider = blobContainerProvider; - this.containerName = containerName; - } - - public async Task DownloadAsync(string id, long version, string suffix, Stream stream) - { - var blobName = GetObjectName(id, suffix); - var blob = blobContainer.GetBlockBlobReference(blobName); - - if (!await blob.ExistsAsync()) - return; - - // look for the requested version - // first check if the original blob has the requested version - if (blob.Metadata.TryGetValue(AssetVersion, out string verionStr)) - { - if (long.TryParse(verionStr, out long blobVersion)) - { - // if not, then look for that snapshot which has the requested version number. - if (blobVersion != version) - { - var snapshotBlob = await FindSnapshotAsync(id, version); - blob = snapshotBlob ?? blob; - } - } - } - - await blob.DownloadToStreamAsync(stream); - } - - public async Task UploadAsync(string id, long version, string suffix, Stream stream) - { - var blobName = GetObjectName(id, suffix); - var blob = blobContainer.GetBlockBlobReference(blobName); - - if (await blob.ExistsAsync()) - { - // if it's already exist create a snapshot, and we overwrite the source blob of the snapshot. - // NOTE: not sure if there is an - await CreateVersioningSnapshotAsync(blob, id, version); - } - - if (!blob.Metadata.ContainsKey(AssetVersion)) - blob.Metadata.Add(AssetVersion, version.ToString()); - else - blob.Metadata[AssetVersion] = version.ToString(); - - blob.Metadata[AssetId] = id; - - await blob.UploadFromStreamAsync(stream); - await blob.SetMetadataAsync(); - } - - public async void Connect() - { - blobContainer = await blobContainerProvider.GetContainerAsync(containerName); - } - - private string GetObjectName(string id, string suffix) - { - Guard.NotNullOrEmpty(id, nameof(id)); - - if (blobContainer == null) - { - throw new InvalidOperationException("No connection established yet."); - } - - var name = $"{id}"; - - if (!string.IsNullOrWhiteSpace(suffix)) - { - name += "_" + suffix; - } - - return name; - } - - private async Task CreateVersioningSnapshotAsync(CloudBlockBlob blob, string id, long version) - { - var metadata = new Dictionary(); - metadata.Add(AssetVersion, version.ToString()); - metadata.Add(AssetId, id); - await blob.CreateSnapshotAsync(metadata, - null, null, null); - } - - private async Task FindSnapshotAsync(string id, long requestedVersion) - { - CloudBlockBlob resultBlob = null; - BlobContinuationToken token = null; - do - { - var listingResult = await blobContainer.ListBlobsSegmentedAsync(null, true, - BlobListingDetails.Snapshots, 10, token, null, null); - token = listingResult.ContinuationToken; - - foreach (CloudBlob snapshotBlob in listingResult.Results.Cast()) - { - if (snapshotBlob.Metadata.TryGetValue(AssetVersion, out string snapshotVersionStr) - && snapshotBlob.Metadata.TryGetValue(AssetId, out string snapshotAssetId) - && long.TryParse(snapshotVersionStr, out long snapshotVersion) - && snapshotVersion == requestedVersion - && snapshotAssetId == id) - { - resultBlob = snapshotBlob as CloudBlockBlob; - break; - } - } - } - while (token != null); - - return resultBlob; - } - } -} diff --git a/src/Squidex.Infrastructure.AzureStorage/BlobContainerProvider.cs b/src/Squidex.Infrastructure.AzureStorage/BlobContainerProvider.cs deleted file mode 100644 index e67ae845f..000000000 --- a/src/Squidex.Infrastructure.AzureStorage/BlobContainerProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ========================================================================== -// BlobContainerProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Microsoft.WindowsAzure.Storage.Blob; - -namespace Squidex.Infrastructure.AzureStorage -{ - public class BlobContainerProvider : IBlobContainerProvider - { - private readonly IStorageAccountManager accountManager; - - public BlobContainerProvider(IStorageAccountManager accountManager) - { - this.accountManager = accountManager; - } - - public async Task GetContainerAsync(string name) - { - var client = accountManager.CreateCloudBlobClient(); - var saneName = name.Replace("@", "-").Replace(".", "-"); - var container = client.GetContainerReference(saneName); - await container.CreateIfNotExistsAsync(); - - return container; - } - } -} diff --git a/src/Squidex.Infrastructure.AzureStorage/IBlobContainerProvider.cs b/src/Squidex.Infrastructure.AzureStorage/IBlobContainerProvider.cs deleted file mode 100644 index 4a14ff169..000000000 --- a/src/Squidex.Infrastructure.AzureStorage/IBlobContainerProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// IBlobContainerProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Microsoft.WindowsAzure.Storage.Blob; - -namespace Squidex.Infrastructure.AzureStorage -{ - public interface IBlobContainerProvider - { - Task GetContainerAsync(string name); - } -} diff --git a/src/Squidex/Config/Domain/AssetStoreModule.cs b/src/Squidex/Config/Domain/AssetStoreModule.cs index 11cd51113..a95d7fb87 100644 --- a/src/Squidex/Config/Domain/AssetStoreModule.cs +++ b/src/Squidex/Config/Domain/AssetStoreModule.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.GoogleCloud; using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.AzureStorage; +using Squidex.Infrastructure.Azure.Storage; // ReSharper disable InvertIf @@ -87,11 +87,7 @@ namespace Squidex.Config.Domain .As() .SingleInstance(); - builder.RegisterType() - .As() - .SingleInstance(); - - builder.Register(c => new AzureBlobAssetStore(c.Resolve(), containerName)) + builder.Register(c => new AzureBlobAssetStore(c.Resolve(), containerName)) .As() .As() .SingleInstance(); diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 4558e0ced..13c65cc5e 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -29,7 +29,7 @@ - +