Skip to content

Commit 43426f0

Browse files
Fix solution build + cleanup and performance fixes
1 parent abf84bc commit 43426f0

10 files changed

Lines changed: 132 additions & 129 deletions

File tree

.gitattributes

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,13 @@
8787
*.eot binary
8888
*.exe binary
8989
*.otf binary
90-
*.pbm binary
9190
*.pdf binary
9291
*.ppt binary
9392
*.pptx binary
9493
*.pvr binary
9594
*.snk binary
9695
*.ttc binary
9796
*.ttf binary
98-
*.wbmp binary
9997
*.woff binary
10098
*.woff2 binary
10199
*.xls binary
@@ -126,3 +124,10 @@
126124
*.dds filter=lfs diff=lfs merge=lfs -text
127125
*.ktx filter=lfs diff=lfs merge=lfs -text
128126
*.ktx2 filter=lfs diff=lfs merge=lfs -text
127+
*.pam filter=lfs diff=lfs merge=lfs -text
128+
*.pbm filter=lfs diff=lfs merge=lfs -text
129+
*.pgm filter=lfs diff=lfs merge=lfs -text
130+
*.ppm filter=lfs diff=lfs merge=lfs -text
131+
*.pnm filter=lfs diff=lfs merge=lfs -text
132+
*.wbmp filter=lfs diff=lfs merge=lfs -text
133+
*.exr filter=lfs diff=lfs merge=lfs -text

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ git submodule update --init --recursive
8686

8787
#### Running the Tests
8888

89-
The unit tests require [Azurite Azure Storage Emulator](https://github.com/Azure/Azurite) and [s3rver](https://github.com/jamhall/s3rver) in order to run.
89+
The unit tests require [Azurite Azure Storage Emulator](https://github.com/Azure/Azurite) and [s3rver](https://github.com/jamhall/s3rver) in order to run.
9090

9191
On Windows to install and run the server as a background process run the following command
9292

codecov.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,14 @@ codecov:
99
# Avoid Report Expired
1010
# https://docs.codecov.io/docs/codecov-yaml#section-expired-reports
1111
max_report_age: off
12+
13+
coverage:
14+
# Use integer precision
15+
# https://docs.codecov.com/docs/codecovyml-reference#coverageprecision
16+
precision: 0
17+
18+
# Explicitly control coverage status checks
19+
# https://docs.codecov.com/docs/commit-status#disabling-a-status
20+
status:
21+
project: on
22+
patch: off

src/Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.targets" />
1818

1919
<ItemGroup>
20+
<PackageReference Update="AWSSDK.S3" Version="3.7.7.16" />
2021
<PackageReference Update="Azure.Storage.Blobs" Version="12.10.0" />
2122
<PackageReference Update="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
2223
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.Collections.Generic;
6+
using System.Globalization;
57
using System.IO;
8+
using System.Threading;
69
using System.Threading.Tasks;
710
using Amazon;
811
using Amazon.S3;
@@ -18,6 +21,12 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS
1821
/// </summary>
1922
public class AWSS3StorageCache : IImageCache
2023
{
24+
private static readonly TaskFactory TaskFactory = new
25+
(CancellationToken.None,
26+
TaskCreationOptions.None,
27+
TaskContinuationOptions.None,
28+
TaskScheduler.Default);
29+
2130
private readonly IAmazonS3 amazonS3Client;
2231
private readonly string bucket;
2332

@@ -30,38 +39,18 @@ public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions)
3039
Guard.NotNull(cacheOptions, nameof(cacheOptions));
3140
AWSS3StorageCacheOptions options = cacheOptions.Value;
3241
this.bucket = options.BucketName;
33-
34-
if (!string.IsNullOrEmpty(options.Endpoint) &&
35-
options.AccessKey != null &&
36-
options.AccessSecret != null)
37-
{
38-
var config = new AmazonS3Config { ServiceURL = options.Endpoint, ForcePathStyle = true };
39-
this.amazonS3Client = new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
40-
}
41-
else if (!string.IsNullOrEmpty(options.AccessKey) &&
42-
!string.IsNullOrEmpty(options.AccessSecret) &&
43-
!string.IsNullOrEmpty(options.Region))
44-
{
45-
var region = RegionEndpoint.GetBySystemName(options.Region);
46-
this.amazonS3Client = new AmazonS3Client(options.AccessKey, options.AccessSecret, region);
47-
}
48-
else
49-
{
50-
var region = RegionEndpoint.GetBySystemName(options.Region);
51-
this.amazonS3Client = new AmazonS3Client(region);
52-
}
42+
this.amazonS3Client = CreateClient(options);
5343
}
5444

5545
/// <inheritdoc/>
5646
public async Task<IImageCacheResolver> GetAsync(string key)
5747
{
58-
var request = new GetObjectMetadataRequest() { BucketName = this.bucket, Key = key };
59-
48+
GetObjectMetadataRequest request = new() { BucketName = this.bucket, Key = key };
6049
try
6150
{
62-
await this.amazonS3Client.GetObjectMetadataAsync(request);
63-
64-
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucket, key);
51+
// HEAD request throws a 404 if not found.
52+
MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata;
53+
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucket, key, metadata);
6554
}
6655
catch
6756
{
@@ -82,7 +71,6 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
8271
};
8372

8473
var dt = metadata.ToDictionary();
85-
8674
foreach (KeyValuePair<string, string> d in dt)
8775
{
8876
request.Metadata.Add(d.Key, d.Value);
@@ -92,46 +80,28 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
9280
}
9381

9482
/// <summary>
95-
/// Creates a new container under the specified account if a container
83+
/// Creates a new bucket under the specified account if a bucket
9684
/// with the same name does not already exist.
9785
/// </summary>
98-
/// <param name="options">The Azure Blob Storage cache options.</param>
86+
/// <param name="options">The AWS S3 Storage cache options.</param>
9987
/// <param name="acl">
10088
/// Specifies whether data in the bucket may be accessed publicly and the level of access.
10189
/// <see cref="S3CannedACL.PublicRead"/> specifies full public read access for bucket
102-
/// and object data. <see cref="S3CannedACL.Private"/> specifies that the container
90+
/// and object data. <see cref="S3CannedACL.Private"/> specifies that the bucket
10391
/// data is private to the account owner.
10492
/// </param>
10593
/// <returns>
106-
/// If the container does not already exist, a <see cref="PutBucketResponse"/> describing the newly
107-
/// created container. If the container already exists, <see langword="null"/>.
94+
/// If the bucket does not already exist, a <see cref="PutBucketResponse"/> describing the newly
95+
/// created bucket. If the container already exists, <see langword="null"/>.
10896
/// </returns>
10997
public static PutBucketResponse CreateIfNotExists(
11098
AWSS3StorageCacheOptions options,
11199
S3CannedACL acl)
112100
{
113-
AmazonS3Client amazonS3Client;
114-
bool foundBucket = false;
115-
116-
if (!string.IsNullOrEmpty(options.Endpoint) &&
117-
options.AccessKey != null &&
118-
options.AccessSecret != null)
119-
{
120-
var config = new AmazonS3Config { ServiceURL = options.Endpoint, ForcePathStyle = true };
121-
amazonS3Client = new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
122-
}
123-
else if (!string.IsNullOrEmpty(options.AccessKey) &&
124-
!string.IsNullOrEmpty(options.AccessSecret))
125-
{
126-
amazonS3Client = new AmazonS3Client(options.AccessKey, options.AccessSecret);
127-
}
128-
else
129-
{
130-
amazonS3Client = new AmazonS3Client(RegionEndpoint.GetBySystemName(options.Region));
131-
}
132-
133-
ListBucketsResponse listBucketsResponse = amazonS3Client.ListBucketsAsync().GetAwaiter().GetResult();
101+
AmazonS3Client client = CreateClient(options);
134102

103+
bool foundBucket = false;
104+
ListBucketsResponse listBucketsResponse = RunSync(() => client.ListBucketsAsync());
135105
foreach (S3Bucket b in listBucketsResponse.Buckets)
136106
{
137107
if (b.BucketName == options.BucketName)
@@ -150,13 +120,57 @@ public static PutBucketResponse CreateIfNotExists(
150120
CannedACL = acl
151121
};
152122

153-
PutBucketResponse putBucketResponse =
154-
amazonS3Client.PutBucketAsync(putBucketRequest).GetAwaiter().GetResult();
155-
156-
return putBucketResponse;
123+
return RunSync(() => client.PutBucketAsync(putBucketRequest));
157124
}
158125

159126
return null;
160127
}
128+
129+
private static AmazonS3Client CreateClient(AWSS3StorageCacheOptions options)
130+
{
131+
if (!string.IsNullOrWhiteSpace(options.Endpoint))
132+
{
133+
// AccessKey can be empty.
134+
// AccessSecret can be empty.
135+
AmazonS3Config config = new() { ServiceURL = options.Endpoint, ForcePathStyle = true };
136+
return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
137+
}
138+
else if (!string.IsNullOrWhiteSpace(options.AccessKey))
139+
{
140+
// AccessSecret can be empty.
141+
Guard.NotNullOrWhiteSpace(options.Region, nameof(options.Region));
142+
var region = RegionEndpoint.GetBySystemName(options.Region);
143+
return new AmazonS3Client(options.AccessKey, options.AccessSecret, region);
144+
}
145+
else if (!string.IsNullOrWhiteSpace(options.Region))
146+
{
147+
var region = RegionEndpoint.GetBySystemName(options.Region);
148+
return new AmazonS3Client(region);
149+
}
150+
else
151+
{
152+
throw new ArgumentException("Invalid configuration.", nameof(options));
153+
}
154+
}
155+
156+
/// <summary>
157+
/// Executes an async <see cref="Task{TResult}"/> method which has
158+
/// a <paramref name="task"/> return type synchronously.
159+
/// <see href="https://github.com/aspnet/AspNetIdentity/blob/b7826741279450c58b230ece98bd04b4815beabf/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs"/>
160+
/// </summary>
161+
/// <typeparam name="TResult">The type of result to return.</typeparam>
162+
/// <param name="task">The task to excecute.</param>
163+
/// <returns>The <typeparamref name="TResult"/>.</returns>
164+
private static TResult RunSync<TResult>(Func<Task<TResult>> task)
165+
{
166+
CultureInfo cultureUi = CultureInfo.CurrentUICulture;
167+
CultureInfo culture = CultureInfo.CurrentCulture;
168+
return TaskFactory.StartNew(() =>
169+
{
170+
Thread.CurrentThread.CurrentCulture = culture;
171+
Thread.CurrentThread.CurrentUICulture = cultureUi;
172+
return task();
173+
}).Unwrap().GetAwaiter().GetResult();
174+
}
161175
}
162176
}
Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,34 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<AssemblyTitle>SixLabors.ImageSharp.Web.Providers.AWS</AssemblyTitle>
5-
<Authors>Six Labors and contributors</Authors>
6-
<Company>Six Labors</Company>
7-
<Copyright>Copyright (c) Six Labors and contributors.</Copyright>
8-
<Product>SixLabors.ImageSharp.Web.Providers.AWS</Product>
9-
<Description>A provider for resolving images via AWS S3.</Description>
10-
<NeutralLanguage>en</NeutralLanguage>
11-
12-
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
13-
<LangVersion>7.3</LangVersion>
14-
15-
<GenerateDocumentationFile>true</GenerateDocumentationFile>
164
<AssemblyName>SixLabors.ImageSharp.Web.Providers.AWS</AssemblyName>
5+
<AssemblyTitle>SixLabors.ImageSharp.Web.Providers.AWS</AssemblyTitle>
6+
<RootNamespace>SixLabors.ImageSharp.Web</RootNamespace>
177
<PackageId>SixLabors.ImageSharp.Web.Providers.AWS</PackageId>
18-
<PackageTags>Image Middleware Resize Crop Gif Jpg Jpeg Bitmap Png Azure</PackageTags>
19-
<PackageIconUrl>https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png</PackageIconUrl>
20-
<PackageProjectUrl>https://github.com/SixLabors/ImageSharp.Web</PackageProjectUrl>
21-
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
22-
<RepositoryType>git</RepositoryType>
23-
<RepositoryUrl>https://github.com/SixLabors/ImageSharp.Web</RepositoryUrl>
8+
<PackageIcon>sixlabors.imagesharp.web.128.png</PackageIcon>
9+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
10+
<RepositoryUrl Condition="'$(RepositoryUrl)' == ''">https://github.com/SixLabors/ImageSharp.Web/</RepositoryUrl>
11+
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
12+
<PackageTags>Image Middleware Resize Crop Gif Jpg Jpeg Bitmap Png AWS</PackageTags>
13+
<Description>A provider for resolving and caching images via AWS Blob Storage.</Description>
14+
</PropertyGroup>
2415

25-
<DebugType Condition="$(codecov) != ''">full</DebugType>
26-
<DebugType Condition="$(codecov) == ''">portable</DebugType>
16+
<PropertyGroup>
17+
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
2718
</PropertyGroup>
2819

2920
<ItemGroup>
30-
<PackageReference Include="AWSSDK.S3" Version="3.7.7.15" />
21+
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp.web\sixlabors.imagesharp.web.128.png" Pack="true" PackagePath="" />
3122
</ItemGroup>
3223

3324
<ItemGroup>
34-
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
25+
<PackageReference Include="AWSSDK.S3" />
3526
</ItemGroup>
3627

3728
<ItemGroup>
3829
<ProjectReference Include="..\ImageSharp.Web\ImageSharp.Web.csproj" />
3930
</ItemGroup>
4031

41-
<PropertyGroup>
42-
<CodeAnalysisRuleSet>..\..\shared-infrastructure\SixLabors.ruleset</CodeAnalysisRuleSet>
43-
<RootNamespace>SixLabors.ImageSharp.Web</RootNamespace>
44-
</PropertyGroup>
45-
46-
<ItemGroup>
47-
<AdditionalFiles Include="..\..\shared-infrastructure\stylecop.json" />
48-
</ItemGroup>
49-
50-
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
51-
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
52-
</PropertyGroup>
53-
5432
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
5533

5634
</Project>

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public class AWSS3StorageImageProvider : IImageProvider
2828
/// <summary>
2929
/// The containers for the blob services.
3030
/// </summary>
31-
private readonly Dictionary<string, IAmazonS3> buckets
32-
= new Dictionary<string, IAmazonS3>();
31+
private readonly Dictionary<string, AmazonS3Client> buckets
32+
= new();
3333

3434
private readonly AWSS3StorageImageProviderOptions storageOptions;
3535
private Func<HttpContext, bool> match;
@@ -54,8 +54,7 @@ public AWSS3StorageImageProvider(IOptions<AWSS3StorageImageProviderOptions> stor
5454

5555
foreach (AWSS3BucketClientOptions bucket in this.storageOptions.S3Buckets)
5656
{
57-
AmazonS3Client s3Client = null;
58-
57+
AmazonS3Client s3Client;
5958
if (!string.IsNullOrEmpty(bucket.Endpoint) &&
6059
bucket.AccessKey != null &&
6160
bucket.AccessSecret != null)
@@ -65,6 +64,7 @@ public AWSS3StorageImageProvider(IOptions<AWSS3StorageImageProviderOptions> stor
6564
ServiceURL = bucket.Endpoint,
6665
ForcePathStyle = true
6766
};
67+
6868
s3Client = new AmazonS3Client(bucket.AccessKey, bucket.AccessSecret, config);
6969
}
7070
else if (!string.IsNullOrEmpty(bucket.AccessKey) &&
@@ -137,17 +137,19 @@ public async Task<IImageResolver> GetAsync(HttpContext context)
137137
return null;
138138
}
139139

140-
bool imageExists = await this.KeyExists(s3Client, bucketName, key);
140+
if (!await this.KeyExists(s3Client, bucketName, key))
141+
{
142+
return null;
143+
}
141144

142-
return !imageExists ? null : new AWSS3StorageImageResolver(s3Client, bucketName, key);
145+
return new AWSS3StorageImageResolver(s3Client, bucketName, key);
143146
}
144147

145148
private bool IsMatch(HttpContext context)
146149
{
147150
// Only match loosly here for performance.
148151
// Path matching conflicts should be dealt with by configuration.
149152
string path = context.Request.Path.Value.TrimStart(SlashChars);
150-
151153
foreach (string bucket in this.buckets.Keys)
152154
{
153155
if (path.StartsWith(bucket, StringComparison.OrdinalIgnoreCase))
@@ -171,7 +173,7 @@ private async Task<bool> KeyExists(IAmazonS3 s3Client, string bucketName, string
171173
};
172174

173175
// If the object doesn't exist then a "NotFound" will be thrown
174-
await s3Client.GetObjectMetadataAsync(request).ConfigureAwait(false);
176+
await s3Client.GetObjectMetadataAsync(request);
175177
return true;
176178
}
177179
catch (AmazonS3Exception e)

0 commit comments

Comments
 (0)