diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs index 79e12a04..676dbeca 100644 --- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs +++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs @@ -17,6 +17,7 @@ public class AWSS3StorageCache : IImageCache { private readonly IAmazonS3 amazonS3Client; private readonly string bucketName; + private readonly string cacheFolder; /// /// Initializes a new instance of the class. @@ -28,17 +29,21 @@ public AWSS3StorageCache(IOptions cacheOptions) AWSS3StorageCacheOptions options = cacheOptions.Value; this.bucketName = options.BucketName; this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options); + this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder) + ? string.Empty + : options.CacheFolder.Trim().Trim('/') + '/'; } /// public async Task GetAsync(string key) { - GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = key }; + string keyWithFolder = this.GetKeyWithFolder(key); + GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = keyWithFolder }; try { // HEAD request throws a 404 if not found. MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata; - return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, key, metadata); + return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, keyWithFolder, metadata); } catch { @@ -52,7 +57,7 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) PutObjectRequest request = new() { BucketName = this.bucketName, - Key = key, + Key = this.GetKeyWithFolder(key), ContentType = metadata.ContentType, InputStream = stream, AutoCloseStream = false @@ -118,6 +123,9 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) return null; } + private string GetKeyWithFolder(string key) + => this.cacheFolder + key; + /// /// /// diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs index 672d16fd..f10f1a47 100644 --- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs @@ -14,6 +14,11 @@ public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions /// public string BucketName { get; set; } = null!; + /// + /// Gets or sets the cache folder's name that'll store cache files under the configured bucket. + /// + public string? CacheFolder { get; set; } + /// public string? AccessKey { get; set; } diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs index 053a947a..05e8190c 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Web.Caching.Azure; public class AzureBlobStorageCache : IImageCache { private readonly BlobContainerClient container; + private readonly string cacheFolder; /// /// Initializes a new instance of the class. @@ -27,12 +28,15 @@ public AzureBlobStorageCache(IOptions cacheOptions AzureBlobStorageCacheOptions options = cacheOptions.Value; this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName); + this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder) + ? string.Empty + : options.CacheFolder.Trim().Trim('/') + '/'; } /// public async Task GetAsync(string key) { - BlobClient blob = this.container.GetBlobClient(key); + BlobClient blob = this.GetBlob(key); if (!await blob.ExistsAsync()) { @@ -45,7 +49,7 @@ public AzureBlobStorageCache(IOptions cacheOptions /// public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) { - BlobClient blob = this.container.GetBlobClient(key); + BlobClient blob = this.GetBlob(key); BlobHttpHeaders headers = new() { @@ -79,4 +83,7 @@ public static Response CreateIfNotExists( AzureBlobStorageCacheOptions options, PublicAccessType accessType) => new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType); + + private BlobClient GetBlob(string key) + => this.container.GetBlobClient(this.cacheFolder + key); } diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs index f44bba41..9db937a9 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs @@ -16,8 +16,15 @@ public class AzureBlobStorageCacheOptions /// /// Gets or sets the Azure Blob Storage container name. - /// Must conform to Azure Blob Storage container naming guidlines. + /// Must conform to Azure Blob Storage container naming guidelines. /// /// public string ContainerName { get; set; } = null!; + + /// + /// Gets or sets the cache folder's name that'll store cache files under the configured container. + /// Must conform to Azure Blob Storage directory naming guidelines. + /// + /// + public string? CacheFolder { get; set; } } diff --git a/tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs b/tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs new file mode 100644 index 00000000..0123e46b --- /dev/null +++ b/tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Web.Tests.TestUtilities; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Web.Tests.Processing; + +public class AWSS3StorageCacheCacheFolderServerTests : ServerTestBase +{ + public AWSS3StorageCacheCacheFolderServerTests(AWSS3StorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper) + : base(fixture, outputHelper, TestConstants.AWSTestImage) + { + } +} diff --git a/tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs b/tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs new file mode 100644 index 00000000..15b089cc --- /dev/null +++ b/tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Web.Tests.TestUtilities; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Web.Tests.Processing; + +public class AzureBlobStorageCacheCacheFolderServerTests : ServerTestBase +{ + public AzureBlobStorageCacheCacheFolderServerTests(AzureBlobStorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper) + : base(fixture, outputHelper, TestConstants.AzureTestImage) + { + } +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs b/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs new file mode 100644 index 00000000..d452ef6b --- /dev/null +++ b/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Amazon.S3; +using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp.Web.Caching.AWS; +using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Providers.AWS; + +namespace SixLabors.ImageSharp.Web.Tests.TestUtilities; + +public class AWSS3StorageCacheCacheFolderTestServerFixture : TestServerFixture +{ + protected override void ConfigureCustomServices(IServiceCollection services, IImageSharpBuilder builder) + => builder + .Configure(o => + o.S3Buckets.Add( + new AWSS3BucketClientOptions + { + Endpoint = TestConstants.AWSEndpoint, + BucketName = TestConstants.AWSBucketName, + AccessKey = TestConstants.AWSAccessKey, + AccessSecret = TestConstants.AWSAccessSecret, + Region = TestConstants.AWSRegion, + Timeout = TestConstants.AWSTimeout, + })) + .AddProvider(AWSS3StorageImageProviderFactory.Create) + .Configure(o => + { + o.Endpoint = TestConstants.AWSEndpoint; + o.BucketName = TestConstants.AWSCacheBucketName; + o.AccessKey = TestConstants.AWSAccessKey; + o.AccessSecret = TestConstants.AWSAccessSecret; + o.Region = TestConstants.AWSRegion; + o.Timeout = TestConstants.AWSTimeout; + o.CacheFolder = TestConstants.AWSCacheFolder; + + AWSS3StorageCache.CreateIfNotExists(o, S3CannedACL.Private); + }) + .SetCache(); +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs b/tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs new file mode 100644 index 00000000..9b678c66 --- /dev/null +++ b/tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Azure.Storage.Blobs.Models; +using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp.Web.Caching.Azure; +using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Providers.Azure; + +namespace SixLabors.ImageSharp.Web.Tests.TestUtilities; + +public class AzureBlobStorageCacheCacheFolderTestServerFixture : TestServerFixture +{ + protected override void ConfigureCustomServices(IServiceCollection services, IImageSharpBuilder builder) + => builder + .Configure(o => + o.BlobContainers.Add( + new AzureBlobContainerClientOptions + { + ConnectionString = TestConstants.AzureConnectionString, + ContainerName = TestConstants.AzureContainerName + })) + .AddProvider(AzureBlobStorageImageProviderFactory.Create) + .Configure(o => + { + o.ConnectionString = TestConstants.AzureConnectionString; + o.ContainerName = TestConstants.AzureCacheContainerName; + o.CacheFolder = TestConstants.AzureCacheFolder; + + AzureBlobStorageCache.CreateIfNotExists(o, PublicAccessType.None); + }) + .SetCache(); +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs index 26c64845..9d9e2554 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs @@ -8,12 +8,14 @@ public static class TestConstants public const string AzureConnectionString = "UseDevelopmentStorage=true"; public const string AzureContainerName = "azure"; public const string AzureCacheContainerName = "is-cache"; + public const string AzureCacheFolder = "cache/folder"; public const string AWSEndpoint = "http://localhost:4568/"; public const string AWSRegion = "eu-west-2"; public const string AWSBucketName = "aws"; public const string AWSCacheBucketName = "aws-cache"; public const string AWSAccessKey = ""; public const string AWSAccessSecret = ""; + public const string AWSCacheFolder = "cache/folder"; public const string ImagePath = "SubFolder/sîxläbörs.îmägéshärp.wéb.png"; public const string PhysicalTestImage = "http://localhost/" + ImagePath; public const string AzureTestImage = "http://localhost/" + AzureContainerName + "/" + ImagePath;