Skip to content

Commit d99f40c

Browse files
Merge branch 'main' into sn/update-shared-infra
2 parents 0c2c992 + a1aec11 commit d99f40c

30 files changed

+291
-100
lines changed

.github/workflows/build-and-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@ jobs:
9595
shell: bash
9696
run: |
9797
sudo npm install -g azurite
98-
sudo azurite --loose &
98+
sudo azurite --loose --skipApiVersionCheck &
9999
100100
- name: Azurite Setup Windows
101101
if: matrix.options.os == 'windows-latest'
102102
shell: bash
103103
run: |
104104
npm install -g azurite
105-
azurite --loose &
105+
azurite --loose --skipApiVersionCheck &
106106
107107
- name: S3rver Setup
108108
if: matrix.options.os != 'windows-latest'

ImageSharp.Web.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1
1616
Directory.Build.targets = Directory.Build.targets
1717
LICENSE = LICENSE
1818
README.md = README.md
19+
SixLabors.ImageSharp.Web.props = SixLabors.ImageSharp.Web.props
1920
shared-infrastructure\SixLabors.ruleset = shared-infrastructure\SixLabors.ruleset
2021
shared-infrastructure\SixLabors.Tests.ruleset = shared-infrastructure\SixLabors.Tests.ruleset
2122
shared-infrastructure\stylecop.json = shared-infrastructure\stylecop.json

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ On Windows to install and run the server as a background process run the followi
9090

9191
```bash
9292
npm install -g azurite
93-
start /B azurite --loose
93+
start /B azurite --loose --skipApiVersionCheck
9494

9595
npm install -g s3rver
9696
start /B s3rver -d .
@@ -100,7 +100,7 @@ On Linux
100100

101101
```bash
102102
sudo npm install -g azurite
103-
sudo azurite --loose &
103+
sudo azurite --loose --skipApiVersionCheck &
104104

105105
sudo npm install -g s3rver
106106
sudo s3rver -d . &

SixLabors.ImageSharp.Web.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33

44
<!--Add common namespaces to implicit global usings if enabled.-->
5-
<ItemGroup Condition="'$(ImplicitUsings)'=='enable' OR '$(ImplicitUsings)'=='true'">
5+
<ItemGroup Condition="'$(UseImageSharp)'=='enable' OR '$(UseImageSharp)'=='true'">
66
<Using Include="SixLabors.ImageSharp" />
77
<Using Include="SixLabors.ImageSharp.Processing" />
88
<Using Include="SixLabors.ImageSharp.Web" />

samples/ImageSharp.Web.Sample/Pages/Index.cshtml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@
5252
</div>
5353
<div>
5454
<p>
55-
<code>sixlabors.imagesharp.web.svg?width=300</code>
55+
<code>sixlabors.imagesharp.web.svg?width=300&format=jpg</code>
5656
</p>
5757
<p>
58-
<img src="sixlabors.imagesharp.web.svg" imagesharp-width="300" />
58+
<img src="sixlabors.imagesharp.web.svg" imagesharp-width="300" imagesharp-format="Format.Jpg" />
5959
</p>
6060
</div>
6161
</section>

samples/ImageSharp.Web.Sample/Pages/_ViewImports.cshtml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
@using SixLabors.ImageSharp
2+
@using SixLabors.ImageSharp.Processing
3+
@using SixLabors.ImageSharp.Web
14
@using ImageSharp.Web.Sample
25
@namespace ImageSharp.Web.Sample.Pages
36
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using Amazon.S3;
5+
6+
namespace SixLabors.ImageSharp.Web;
7+
8+
/// <summary>
9+
/// Represents a scoped Amazon S3 client instance that is explicitly associated with a single S3 bucket.
10+
/// This wrapper provides a strongly-typed link between the client and the bucket it operates on,
11+
/// and optionally manages the lifetime of the underlying <see cref="AmazonS3Client"/>.
12+
/// </summary>
13+
public sealed class AmazonS3BucketClient : IDisposable
14+
{
15+
private readonly bool disposeClient;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="AmazonS3BucketClient"/> class.
19+
/// </summary>
20+
/// <param name="bucketName">
21+
/// The bucket name associated with this client instance.
22+
/// </param>
23+
/// <param name="client">
24+
/// The underlying Amazon S3 client instance. This should be an already configured instance of <see cref="AmazonS3Client"/>.
25+
/// </param>
26+
/// <param name="disposeClient">
27+
/// A value indicating whether the underlying client should be disposed when this instance is disposed.
28+
/// </param>
29+
public AmazonS3BucketClient(string bucketName, AmazonS3Client client, bool disposeClient = true)
30+
{
31+
Guard.NotNullOrWhiteSpace(bucketName, nameof(bucketName));
32+
Guard.NotNull(client, nameof(client));
33+
this.BucketName = bucketName;
34+
this.Client = client;
35+
this.disposeClient = disposeClient;
36+
}
37+
38+
/// <summary>
39+
/// Gets the bucket name associated with this client instance.
40+
/// </summary>
41+
public string BucketName { get; }
42+
43+
/// <summary>
44+
/// Gets the underlying Amazon S3 client instance.
45+
/// </summary>
46+
public AmazonS3Client Client { get; }
47+
48+
/// <inheritdoc/>
49+
public void Dispose()
50+
{
51+
if (this.disposeClient)
52+
{
53+
this.Client.Dispose();
54+
}
55+
}
56+
}

src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal static class AmazonS3ClientFactory
1818
/// A new <see cref="AmazonS3Client"/>.
1919
/// </returns>
2020
/// <exception cref="ArgumentException">Invalid configuration.</exception>
21-
public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options)
21+
public static AmazonS3BucketClient CreateClient(IAWSS3BucketClientOptions options)
2222
{
2323
if (!string.IsNullOrWhiteSpace(options.Endpoint))
2424
{
@@ -27,7 +27,7 @@ public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options)
2727
// PathStyle endpoint doesn't support AccelerateEndpoint.
2828
AmazonS3Config config = new() { ServiceURL = options.Endpoint, ForcePathStyle = true, AuthenticationRegion = options.Region };
2929
SetTimeout(config, options.Timeout);
30-
return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
30+
return new(options.BucketName, new AmazonS3Client(options.AccessKey, options.AccessSecret, config));
3131
}
3232
else if (!string.IsNullOrWhiteSpace(options.AccessKey))
3333
{
@@ -36,14 +36,14 @@ public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options)
3636
RegionEndpoint region = RegionEndpoint.GetBySystemName(options.Region);
3737
AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint };
3838
SetTimeout(config, options.Timeout);
39-
return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
39+
return new(options.BucketName, new AmazonS3Client(options.AccessKey, options.AccessSecret, config));
4040
}
4141
else if (!string.IsNullOrWhiteSpace(options.Region))
4242
{
4343
RegionEndpoint region = RegionEndpoint.GetBySystemName(options.Region);
4444
AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint };
4545
SetTimeout(config, options.Timeout);
46-
return new AmazonS3Client(config);
46+
return new(options.BucketName, new AmazonS3Client(config));
4747
}
4848
else
4949
{

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,29 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS;
1313
/// <summary>
1414
/// Implements an AWS S3 Storage based cache.
1515
/// </summary>
16-
public class AWSS3StorageCache : IImageCache
16+
public class AWSS3StorageCache : IImageCache, IDisposable
1717
{
18-
private readonly AmazonS3Client amazonS3Client;
18+
private readonly AmazonS3BucketClient amazonS3Client;
1919
private readonly string bucketName;
2020
private readonly string cacheFolder;
21+
private bool isDisposed;
2122

2223
/// <summary>
2324
/// Initializes a new instance of the <see cref="AWSS3StorageCache"/> class.
2425
/// </summary>
2526
/// <param name="cacheOptions">The cache options.</param>
26-
public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions)
27+
/// <param name="serviceProvider">The current service provider.</param>
28+
public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions, IServiceProvider serviceProvider)
2729
{
2830
Guard.NotNull(cacheOptions, nameof(cacheOptions));
2931
AWSS3StorageCacheOptions options = cacheOptions.Value;
30-
this.bucketName = options.BucketName;
31-
this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options);
32+
33+
this.amazonS3Client =
34+
options.S3ClientFactory?.Invoke(options, serviceProvider)
35+
?? AmazonS3ClientFactory.CreateClient(options);
36+
37+
this.bucketName = this.amazonS3Client.BucketName;
38+
3239
this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder)
3340
? string.Empty
3441
: options.CacheFolder.Trim().Trim('/') + '/';
@@ -42,8 +49,8 @@ public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions)
4249
try
4350
{
4451
// HEAD request throws a 404 if not found.
45-
MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata;
46-
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, keyWithFolder, metadata);
52+
MetadataCollection metadata = (await this.amazonS3Client.Client.GetObjectMetadataAsync(request)).Metadata;
53+
return new AWSS3StorageCacheResolver(this.amazonS3Client.Client, this.bucketName, keyWithFolder, metadata);
4754
}
4855
catch
4956
{
@@ -60,15 +67,16 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
6067
Key = this.GetKeyWithFolder(key),
6168
ContentType = metadata.ContentType,
6269
InputStream = stream,
63-
AutoCloseStream = false
70+
AutoCloseStream = false,
71+
UseChunkEncoding = false
6472
};
6573

6674
foreach (KeyValuePair<string, string> d in metadata.ToDictionary())
6775
{
6876
request.Metadata.Add(d.Key, d.Value);
6977
}
7078

71-
return this.amazonS3Client.PutObjectAsync(request);
79+
return this.amazonS3Client.Client.PutObjectAsync(request);
7280
}
7381

7482
/// <summary>
@@ -86,16 +94,38 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
8694
/// If the bucket does not already exist, a <see cref="PutBucketResponse"/> describing the newly
8795
/// created bucket. If the container already exists, <see langword="null"/>.
8896
/// </returns>
89-
public static PutBucketResponse? CreateIfNotExists(
90-
AWSS3StorageCacheOptions options,
91-
S3CannedACL acl)
97+
public static PutBucketResponse? CreateIfNotExists(AWSS3StorageCacheOptions options, S3CannedACL acl)
9298
=> AsyncHelper.RunSync(() => CreateIfNotExistsAsync(options, acl));
9399

94-
private static async Task<PutBucketResponse?> CreateIfNotExistsAsync(
95-
AWSS3StorageCacheOptions options,
96-
S3CannedACL acl)
100+
/// <summary>
101+
/// Releases the unmanaged resources used by the <see cref="AWSS3StorageCache"/> and optionally releases the managed resources.
102+
/// </summary>
103+
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
104+
protected virtual void Dispose(bool disposing)
105+
{
106+
if (!this.isDisposed)
107+
{
108+
if (disposing)
109+
{
110+
this.amazonS3Client?.Dispose();
111+
}
112+
113+
this.isDisposed = true;
114+
}
115+
}
116+
117+
/// <inheritdoc/>
118+
public void Dispose()
119+
{
120+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
121+
this.Dispose(disposing: true);
122+
GC.SuppressFinalize(this);
123+
}
124+
125+
private static async Task<PutBucketResponse?> CreateIfNotExistsAsync(AWSS3StorageCacheOptions options, S3CannedACL acl)
97126
{
98-
AmazonS3Client client = AmazonS3ClientFactory.CreateClient(options);
127+
using AmazonS3BucketClient bucketClient = AmazonS3ClientFactory.CreateClient(options);
128+
AmazonS3Client client = bucketClient.Client;
99129

100130
bool foundBucket = false;
101131
ListBucketsResponse listBucketsResponse = await client.ListBucketsAsync();
@@ -141,7 +171,7 @@ private static readonly TaskFactory TaskFactory
141171
/// <summary>
142172
/// Executes an async <see cref="Task"/> method synchronously.
143173
/// </summary>
144-
/// <param name="task">The task to excecute.</param>
174+
/// <param name="task">The task to execute.</param>
145175
public static void RunSync(Func<Task> task)
146176
{
147177
CultureInfo cultureUi = CultureInfo.CurrentUICulture;
@@ -159,7 +189,7 @@ public static void RunSync(Func<Task> task)
159189
/// a <paramref name="task"/> return type synchronously.
160190
/// </summary>
161191
/// <typeparam name="TResult">The type of result to return.</typeparam>
162-
/// <param name="task">The task to excecute.</param>
192+
/// <param name="task">The task to execute.</param>
163193
/// <returns>The <typeparamref name="TResult"/>.</returns>
164194
public static TResult RunSync<TResult>(Func<Task<TResult>> task)
165195
{

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS;
88
/// </summary>
99
public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
1010
{
11+
/// <inheritdoc/>
12+
public Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3BucketClient>? S3ClientFactory { get; set; }
13+
1114
/// <inheritdoc/>
1215
public string? Region { get; set; }
1316

0 commit comments

Comments
 (0)