+
[](https://github.com/SixLabors/ImageSharp.Web/actions)
[](https://codecov.io/gh/SixLabors/ImageSharp.Web)
[](https://opensource.org/licenses/Apache-2.0)
From 33346090d65e202c3d42b0d1a5eec1c4db5a1939 Mon Sep 17 00:00:00 2001
From: James Jackson-South
Date: Wed, 8 Feb 2023 20:50:35 +1000
Subject: [PATCH 038/102] Disallow data URIs
---
src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs b/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
index 01566ffb..04a51c24 100644
--- a/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
+++ b/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
@@ -198,8 +198,9 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
Guard.NotNull(output, nameof(output));
string src = output.Attributes[SrcAttributeName]?.Value as string ?? this.Src;
- if (string.IsNullOrWhiteSpace(src))
+ if (string.IsNullOrWhiteSpace(src) || src.StartsWith("data", StringComparison.OrdinalIgnoreCase))
{
+ base.Process(context, output);
return;
}
From 1743f6d999e1567cfc99524e691e0adf880be782 Mon Sep 17 00:00:00 2001
From: James Jackson-South
Date: Wed, 8 Feb 2023 21:45:07 +1000
Subject: [PATCH 039/102] Update target framework, infrastructure, and fix
build warnings
---
.editorconfig | 33 +-
.github/workflows/build-and-test.yml | 14 +-
LICENSE | 216 +---
README.md | 12 +-
.../ImageSharp.Web.Sample.csproj | 2 +-
samples/ImageSharp.Web.Sample/Program.cs | 38 +-
samples/ImageSharp.Web.Sample/Startup.cs | 234 ++---
shared-infrastructure | 2 +-
.../AmazonS3ClientFactory.cs | 95 +-
.../Caching/AWSS3StorageCache.cs | 261 +++--
.../Caching/AWSS3StorageCacheOptions.cs | 45 +-
.../IAWSS3BucketClientOptions.cs | 94 +-
.../ImageSharp.Web.Providers.AWS.csproj | 18 +-
.../Providers/AWSS3StorageImageProvider.cs | 251 +++--
.../AWSS3StorageImageProviderOptions.cs | 61 +-
.../Resolvers/AWSS3StorageCacheResolver.cs | 73 +-
.../Resolvers/AWSS3StorageImageResolver.cs | 77 +-
.../Caching/AzureBlobStorageCache.cs | 120 ++-
.../Caching/AzureBlobStorageCacheOptions.cs | 24 +-
.../IAzureBlobContainerClientOptions.cs | 34 +-
.../ImageSharp.Web.Providers.Azure.csproj | 18 +-
.../AzureBlobStorageImageProvider.cs | 221 ++--
.../AzureBlobStorageImageProviderOptions.cs | 40 +-
.../AzureBlobStorageCacheResolver.cs | 50 +-
.../AzureBlobStorageImageResolver.cs | 65 +-
src/ImageSharp.Web/AsyncHelper.cs | 87 +-
src/ImageSharp.Web/Caching/HexEncoder.cs | 84 +-
src/ImageSharp.Web/Caching/ICacheHash.cs | 26 +-
src/ImageSharp.Web/Caching/ICacheKey.cs | 30 +-
src/ImageSharp.Web/Caching/IImageCache.cs | 42 +-
.../Caching/LegacyV1CacheKey.cs | 49 +-
.../ConcurrentTLruCache{TKey,TValue}.cs | 563 +++++-----
.../Caching/LruCache/ItemDestination.cs | 15 +-
.../LongTickCountLruItem{TKey,TValue}.cs | 56 +-
.../TLruLongTicksPolicy{TKey,TValue}.cs | 127 ++-
.../Caching/PhysicalFileSystemCache.cs | 308 +++---
.../Caching/PhysicalFileSystemCacheOptions.cs | 54 +-
src/ImageSharp.Web/Caching/SHA256CacheHash.cs | 91 +-
.../Caching/UriAbsoluteCacheKey.cs | 32 +-
.../UriAbsoluteLowerInvariantCacheKey.cs | 32 +-
.../Caching/UriRelativeCacheKey.cs | 22 +-
.../UriRelativeLowerInvariantCacheKey.cs | 22 +-
src/ImageSharp.Web/CaseHandlingUriBuilder.cs | 399 ++++----
src/ImageSharp.Web/CommandHandling.cs | 30 +-
.../Commands/CommandCollection.cs | 280 +++--
.../Commands/CommandCollectionExtensions.cs | 30 +-
src/ImageSharp.Web/Commands/CommandParser.cs | 107 +-
.../Commands/Converters/ArrayConverter{T}.cs | 75 +-
.../Commands/Converters/ColorConverter.cs | 140 ++-
.../Commands/Converters/EnumConverter.cs | 104 +-
.../Commands/Converters/ICommandConverter.cs | 61 +-
.../Converters/IntegralNumberConverter{T}.cs | 119 ++-
.../Commands/Converters/ListConverter{T}.cs | 75 +-
.../Converters/SimpleCommandConverter{T}.cs | 61 +-
src/ImageSharp.Web/Commands/IRequestParser.cs | 25 +-
.../PresetOnlyQueryCollectionRequestParser.cs | 109 +-
...OnlyQueryCollectionRequestParserOptions.cs | 20 +-
.../Commands/QueryCollectionRequestParser.cs | 45 +-
src/ImageSharp.Web/Commands/TypeConstants.cs | 104 +-
.../ApplicationBuilderExtensions.cs | 28 +-
.../DependencyInjection/IImageSharpBuilder.cs | 20 +-
.../DependencyInjection/ImageSharpBuilder.cs | 26 +-
.../ImageSharpBuilderExtensions.cs | 624 ++++++------
.../ServiceCollectionExtensions.cs | 219 ++--
.../ExifOrientationUtilities.cs | 432 ++++----
src/ImageSharp.Web/FormatUtilities.cs | 151 ++-
src/ImageSharp.Web/FormattedImage.cs | 293 +++---
src/ImageSharp.Web/HMACUtilities.cs | 149 ++-
src/ImageSharp.Web/ImageCacheMetadata.cs | 364 ++++---
src/ImageSharp.Web/ImageMetadata.cs | 171 ++--
src/ImageSharp.Web/ImageSharp.Web.csproj | 18 +-
...ImageSharpRequestAuthorizationUtilities.cs | 443 ++++----
.../Middleware/ImageCommandContext.cs | 78 +-
src/ImageSharp.Web/Middleware/ImageContext.cs | 444 ++++----
.../Middleware/ImageProcessingContext.cs | 102 +-
.../Middleware/ImageSharpMiddleware.cs | 961 +++++++++---------
.../Middleware/ImageSharpMiddlewareOptions.cs | 272 +++--
.../Middleware/ImageWorkerResult.cs | 74 +-
.../Middleware/LoggerExtensions.cs | 167 ++-
.../Middleware/ResponseConstants.cs | 39 +-
src/ImageSharp.Web/PathUtilities.cs | 36 +-
.../Processors/AutoOrientWebProcessor.cs | 69 +-
.../Processors/BackgroundColorWebProcessor.cs | 71 +-
.../Processors/FormatWebProcessor.cs | 91 +-
.../Processors/IImageWebProcessor.cs | 83 +-
.../Processors/QualityWebProcessor.cs | 125 ++-
.../Processors/ResizeWebProcessor.cs | 405 ++++----
.../Processors/WebProcessingExtensions.cs | 184 ++--
.../Providers/FileProviderImageProvider.cs | 89 +-
.../Providers/IImageProvider.cs | 58 +-
.../Providers/PhysicalFileSystemProvider.cs | 90 +-
.../PhysicalFileSystemProviderOptions.cs | 46 +-
.../Providers/ProcessingBehavior.cs | 27 +-
.../Providers/WebRootImageProvider.cs | 30 +-
.../Resolvers/FileProviderImageResolver.cs | 36 +-
.../Resolvers/IImageCacheResolver.cs | 33 +-
.../Resolvers/IImageResolver.cs | 33 +-
.../PhysicalFileSystemCacheResolver.cs | 102 +-
.../Synchronization/AsyncKeyLock.cs | 119 ++-
.../AsyncKeyReaderWriterLock.cs | 109 +-
.../Synchronization/AsyncLock.cs | 133 ++-
.../Synchronization/AsyncReaderWriterLock.cs | 331 +++---
.../RefCountedConcurrentDictionary.cs | 363 ++++---
.../Caching/CacheHashBaseline.cs | 47 +-
.../Caching/CacheHashBenchmarks.cs | 59 +-
.../Caching/CacheKeyBenchmarks.cs | 96 +-
.../Caching/StringJoinBenchmarks.cs | 86 +-
.../Caching/ToHexBenchmarks.cs | 120 ++-
.../ImageSharp.Web.Benchmarks.csproj | 14 +-
.../ImageSharp.Web.Benchmarks/MemoryConfig.cs | 11 +-
tests/ImageSharp.Web.Benchmarks/Program.cs | 13 +-
.../Synchronization/AsyncLockBenchmarks.cs | 99 +-
...efCountedConcurrentDictionaryBenchmarks.cs | 117 ++-
.../Caching/CacheHashTests.cs | 92 +-
.../Caching/CacheKeyTests.cs | 151 ++-
.../Caching/HexEncoderTests.cs | 50 +-
.../Caching/ImageCacheMetadataTests.cs | 101 +-
.../Caching/ImageMetaDataTests.cs | 61 +-
.../Caching/PhysicialFileSystemCacheTests.cs | 91 +-
.../Commands/CommandCollectionTests.cs | 336 +++---
.../Commands/CommandParserTests.cs | 436 ++++----
...etOnlyQueryCollectionRequestParserTests.cs | 138 ++-
.../Commands/QueryCollectionUriParserTests.cs | 44 +-
.../DependencyInjection/MockCacheHash.cs | 11 +-
.../DependencyInjection/MockCacheKey.cs | 11 +-
.../MockCommandConverter.cs | 24 +-
.../DependencyInjection/MockImageCache.cs | 15 +-
.../DependencyInjection/MockImageProvider.cs | 19 +-
.../DependencyInjection/MockRequestParser.cs | 11 +-
.../DependencyInjection/MockWebProcessor.cs | 29 +-
.../ServiceRegistrationExtensionsTests.cs | 793 +++++++--------
.../Helpers/CaseHandlingUriBuilderTests.cs | 41 +-
.../Helpers/ExifOrientationUtilitiesTests.cs | 298 +++---
.../Helpers/FormatUtilitiesTests.cs | 94 +-
.../Helpers/HMACUtilitiesTests.cs | 79 +-
.../ImageSharp.Web.Tests.csproj | 14 +-
.../AWSS3StorageCacheServerTests.cs | 13 +-
.../AzureBlobStorageCacheServerTests.cs | 13 +-
...FileSystemCacheAuthenticatedServerTests.cs | 13 +-
.../PhysicalFileSystemCacheServerTests.cs | 13 +-
.../Processors/AutoOrientWebProcessorTests.cs | 48 +-
.../BackgroundColorWebProcessorTests.cs | 64 +-
.../Processors/FormatWebProcessorTests.cs | 82 +-
.../Processors/FormattedImageTests.cs | 91 +-
.../Processors/QualityWebProcessorTests.cs | 120 ++-
.../Processors/ResizeWebProcessorTests.cs | 494 +++++----
.../WebProcessingExtensionsTests.cs | 75 +-
.../PhysicalFileSystemProviderTests.cs | 79 +-
.../Synchronization/AsyncKeyLockTests.cs | 78 +-
.../AsyncKeyReaderWriterLockTests.cs | 180 ++--
.../Synchronization/AsyncLockTests.cs | 36 +-
.../AsyncReaderWriterLockTests.cs | 140 ++-
.../RefCountedConcurrentDictionaryTests.cs | 237 +++--
.../AWSS3StorageCacheTestServerFixture.cs | 57 +-
.../AWSS3StorageImageProviderFactory.cs | 151 ++-
.../AuthenticatedServerTestBase.cs | 100 +-
.../AuthenticatedTestServerFixture.cs | 25 +-
.../AzureBlobStorageCacheTestServerFixture.cs | 41 +-
.../AzureBlobStorageImageProviderFactory.cs | 76 +-
.../TestUtilities/CacheBusterWebProcessor.cs | 30 +-
...stemCacheAuthenticatedTestServerFixture.cs | 17 +-
...hysicalFileSystemCacheTestServerFixture.cs | 17 +-
.../TestUtilities/ServerTestBase.cs | 203 ++--
.../TestUtilities/TestConstants.cs | 37 +-
.../TestUtilities/TestEnvironment.cs | 39 +-
.../TestUtilities/TestServerFixture.cs | 204 ++--
166 files changed, 9590 insertions(+), 10044 deletions(-)
diff --git a/.editorconfig b/.editorconfig
index 33fd0577..2e3045fb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,5 @@
-# Version: 2.1.0 (Using https://semver.org/)
-# Updated: 2021-03-03
+# Version: 4.1.1 (Using https://semver.org/)
+# Updated: 2022-05-23
# See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
# See https://github.com/RehanSaeed/EditorConfig for updates to this file.
# See http://EditorConfig.org for more information about .editorconfig files.
@@ -49,11 +49,11 @@ indent_size = 2
indent_size = 2
# Markdown Files
-[*.md]
+[*.{md,mdx}]
trim_trailing_whitespace = false
# Web Files
-[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}]
+[*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}]
indent_size = 2
# Batch Files
@@ -75,7 +75,7 @@ indent_style = tab
[*.{cs,csx,cake,vb,vbx}]
# Default Severity for all .NET Code Style rules below
-dotnet_analyzer_diagnostic.category-style.severity = warning
+dotnet_analyzer_diagnostic.severity = warning
##########################################
# Language Rules
@@ -122,20 +122,21 @@ dotnet_style_coalesce_expression = true:warning
dotnet_style_null_propagation = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
# File header preferences
-file_header_template = Copyright (c) Six Labors.\nLicensed under the Apache License, Version 2.0.
+file_header_template = Copyright (c) Six Labors.\nLicensed under the Six Labors Split License.
# SA1636: File header copyright text should match
# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project.
# dotnet_diagnostic.SA1636.severity = none
# Undocumented
-dotnet_style_operator_placement_when_wrapping = end_of_line
+dotnet_style_operator_placement_when_wrapping = end_of_line:warning
+csharp_style_prefer_null_check_over_type_check = true:warning
# C# Style Rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
[*.{cs,csx,cake}]
# 'var' preferences
-csharp_style_var_for_built_in_types = never
-csharp_style_var_when_type_is_apparent = true:warning
+csharp_style_var_for_built_in_types = false:warning
+csharp_style_var_when_type_is_apparent = false:warning
csharp_style_var_elsewhere = false:warning
# Expression-bodied members
csharp_style_expression_bodied_methods = true:warning
@@ -200,12 +201,15 @@ dotnet_diagnostic.IDE0059.severity = suggestion
# Organize using directives
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
+# Dotnet namespace options
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_diagnostic.IDE0130.severity = suggestion
# C# formatting rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules
[*.{cs,csx,cake}]
# Newline options
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#new-line-options
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
@@ -214,7 +218,7 @@ csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation options
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#indentation-options
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = no_change
@@ -222,7 +226,7 @@ csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents_when_block = false
# Spacing options
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#spacing-options
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_parentheses = false
@@ -246,9 +250,12 @@ csharp_space_before_open_square_brackets = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_square_brackets = false
# Wrap options
-# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#wrap-options
csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true
+# Namespace options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#namespace-options
+csharp_style_namespace_declarations = file_scoped:warning
##########################################
# .NET Naming Rules
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index d9cf46dd..4b8d6d39 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -16,27 +16,27 @@ jobs:
matrix:
options:
- os: ubuntu-latest
- framework: net6.0
+ framework: net7.0
runtime: -x64
codecov: false
- os: macos-latest
- framework: net6.0
+ framework: net7.0
runtime: -x64
codecov: false
- os: windows-latest
- framework: net6.0
+ framework: net7.0
runtime: -x64
codecov: true
- os: ubuntu-latest
- framework: netcoreapp3.1
+ framework: net6.0
runtime: -x64
codecov: false
- os: macos-latest
- framework: netcoreapp3.1
+ framework: net6.0
runtime: -x64
codecov: false
- os: windows-latest
- framework: netcoreapp3.1
+ framework: net6.0
runtime: -x64
codecov: false
@@ -112,8 +112,8 @@ jobs:
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
+ 7.0.x
6.0.x
- 3.1.x
- name: DotNet Build
shell: pwsh
diff --git a/LICENSE b/LICENSE
index 8d5852d3..a68eb678 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,43 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
+Six Labors Split License
+Version 1.0, June 2022
+Copyright (c) Six Labors
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
- 1. Definitions.
+1. Definitions.
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source
+ code, documentation source, and configuration files.
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including
+ but not limited to compiled object code, generated documentation, and conversions to other media types.
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
+ "Work" (or "Works") shall mean any Six Labors software made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work.
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
+ "Direct Package Dependency" shall mean any Work in Source or Object form that is installed directly by You.
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
+ "Transitive Package Dependency" shall mean any Work in Object form that is installed indirectly by a third party
+ dependency unrelated to Six Labors.
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
+2. License
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
+ Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a
+ Six Labors Commercial Use License.
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
+ Licenses are granted based upon You meeting the qualified criteria as stated. Once granted,
+ You must reference the granted license only in all documentation.
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
+ Works in Source or Object form are licensed to You under the Apache License, Version 2.0 if.
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
+ - You are consuming the Work in for use in software licensed under an Open Source or Source Available license.
+ - You are consuming the Work as a Transitive Package Dependency.
+ - You are consuming the Work as a Direct Package Dependency in the capacity of a For-profit company/individual with
+ less than 1M USD annual gross revenue.
+ - You are consuming the Work as a Direct Package Dependency in the capacity of a Non-profit organization
+ or Registered Charity.
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright (c) Six Labors
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ For all other scenarios, Works in Source or Object form are licensed to You under the Six Labors Commercial License
+ which may be purchased by visiting https://sixlabors.com/pricing/.
diff --git a/README.md b/README.md
index 6c52a299..0ded0990 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ SixLabors.ImageSharp.Web
[](https://github.com/SixLabors/ImageSharp.Web/actions)
[](https://codecov.io/gh/SixLabors/ImageSharp.Web)
-[](https://opensource.org/licenses/Apache-2.0)
+[](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE)
[](https://github.com/SixLabors/ImageSharp.Web/issues)
[](https://github.com/SixLabors/ImageSharp.Web/stargazers)
@@ -17,13 +17,11 @@ SixLabors.ImageSharp.Web
[](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
-### **ImageSharp.Web** is a new high-performance ASP.NET Core middleware leveraging the ImageSharp graphics library to allow on-the-fly image manipulation via URL based commands.
+### **ImageSharp.Web** is a high-performance ASP.NET Core middleware leveraging the ImageSharp graphics library to allow on-the-fly image manipulation via URL based commands.
## License
-- ImageSharp.Web is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
-- An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
-Please visit https://sixlabors.com/pricing for details.
+- ImageSharp.Web is licensed under the [Six Labors Split License, Version 1.0](https://github.com/SixLabors/ImageSharp/blob/main/LICEN
## Support Six Labors
@@ -57,9 +55,9 @@ Install stable releases via Nuget; development releases are available via MyGet.
If you prefer, you can compile ImageSharp.Web yourself (please do and help!)
-- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/)
+- Using [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed
- - Make sure you have [the .NET Core 3.1 SDK](https://www.microsoft.com/net/core#windows) installed
+ - Make sure you have [the .NET 6 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
diff --git a/samples/ImageSharp.Web.Sample/ImageSharp.Web.Sample.csproj b/samples/ImageSharp.Web.Sample/ImageSharp.Web.Sample.csproj
index 744de963..9c2462b4 100644
--- a/samples/ImageSharp.Web.Sample/ImageSharp.Web.Sample.csproj
+++ b/samples/ImageSharp.Web.Sample/ImageSharp.Web.Sample.csproj
@@ -1,7 +1,7 @@
- netcoreapp3.1
+ net6.0
diff --git a/samples/ImageSharp.Web.Sample/Program.cs b/samples/ImageSharp.Web.Sample/Program.cs
index 9c4bb394..d691ebeb 100644
--- a/samples/ImageSharp.Web.Sample/Program.cs
+++ b/samples/ImageSharp.Web.Sample/Program.cs
@@ -1,29 +1,25 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Hosting;
+namespace SixLabors.ImageSharp.Web.Sample;
-namespace SixLabors.ImageSharp.Web.Sample
+///
+/// The running application.
+///
+public static class Program
{
///
- /// The running application.
+ /// The main entry point to the running application.
///
- public static class Program
- {
- ///
- /// The main entry point to the running application.
- ///
- /// Any arguments to pass to the application.
- public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
+ /// Any arguments to pass to the application.
+ public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
- ///
- /// Creates an instance used to create a configured .
- ///
- /// Any arguments to pass to the application.
- /// The .
- public static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup());
- }
+ ///
+ /// Creates an instance used to create a configured .
+ ///
+ /// Any arguments to pass to the application.
+ /// The .
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup());
}
diff --git a/samples/ImageSharp.Web.Sample/Startup.cs b/samples/ImageSharp.Web.Sample/Startup.cs
index 26bca69c..eaf01e31 100644
--- a/samples/ImageSharp.Web.Sample/Startup.cs
+++ b/samples/ImageSharp.Web.Sample/Startup.cs
@@ -1,150 +1,142 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
-using System;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
using SixLabors.ImageSharp.Web.Caching;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.DependencyInjection;
using SixLabors.ImageSharp.Web.Processors;
using SixLabors.ImageSharp.Web.Providers;
-namespace SixLabors.ImageSharp.Web.Sample
+namespace SixLabors.ImageSharp.Web.Sample;
+
+///
+/// Contains application configuration allowing the addition of services to the container.
+///
+public class Startup
{
///
- /// Contains application configuration allowing the addition of services to the container.
+ /// Initializes a new instance of the class.
///
- public class Startup
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration properties.
- public Startup(IConfiguration configuration) => this.AppConfiguration = configuration;
+ /// The configuration properties.
+ public Startup(IConfiguration configuration) => this.AppConfiguration = configuration;
- ///
- /// Gets the configuration properties.
- ///
- public IConfiguration AppConfiguration { get; }
+ ///
+ /// Gets the configuration properties.
+ ///
+ public IConfiguration AppConfiguration { get; }
- ///
- /// This method gets called by the runtime. Use this method to add services to the container.
- ///
- /// The collection of service descriptors.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddImageSharp()
- .SetRequestParser()
- .Configure(options =>
- {
- options.CacheRootPath = null;
- options.CacheFolder = "is-cache";
- options.CacheFolderDepth = 8;
- })
- .SetCache()
- .SetCacheKey()
- .SetCacheHash()
- .Configure(options =>
- {
- options.ProviderRootPath = null;
- })
- .AddProvider()
- .AddProcessor()
- .AddProcessor()
- .AddProcessor()
- .AddProcessor()
- .AddProcessor();
+ ///
+ /// This method gets called by the runtime. Use this method to add services to the container.
+ ///
+ /// The collection of service descriptors.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddImageSharp()
+ .SetRequestParser()
+ .Configure(options =>
+ {
+ options.CacheRootPath = null;
+ options.CacheFolder = "is-cache";
+ options.CacheFolderDepth = 8;
+ })
+ .SetCache()
+ .SetCacheKey()
+ .SetCacheHash()
+ .Configure(options =>
+ {
+ options.ProviderRootPath = null;
+ })
+ .AddProvider()
+ .AddProcessor()
+ .AddProcessor()
+ .AddProcessor()
+ .AddProcessor()
+ .AddProcessor();
- // Add the default service and options.
- //
- // services.AddImageSharp();
+ // Add the default service and options.
+ //
+ // services.AddImageSharp();
- // Or add the default service and custom options.
- //
- // this.ConfigureDefaultServicesAndCustomOptions(services);
+ // Or add the default service and custom options.
+ //
+ // this.ConfigureDefaultServicesAndCustomOptions(services);
- // Or we can fine-grain control adding the default options and configure all other services.
- //
- // this.ConfigureCustomServicesAndDefaultOptions(services);
+ // Or we can fine-grain control adding the default options and configure all other services.
+ //
+ // this.ConfigureCustomServicesAndDefaultOptions(services);
- // Or we can fine-grain control adding custom options and configure all other services
- // There are also factory methods for each builder that will allow building from configuration files.
- //
- // this.ConfigureCustomServicesAndCustomOptions(services);
- }
+ // Or we can fine-grain control adding custom options and configure all other services
+ // There are also factory methods for each builder that will allow building from configuration files.
+ //
+ // this.ConfigureCustomServicesAndCustomOptions(services);
+ }
- private void ConfigureDefaultServicesAndCustomOptions(IServiceCollection services)
+ private void ConfigureDefaultServicesAndCustomOptions(IServiceCollection services)
+ {
+ services.AddImageSharp(options =>
{
- services.AddImageSharp(options =>
- {
- options.Configuration = Configuration.Default;
- options.BrowserMaxAge = TimeSpan.FromDays(7);
- options.CacheMaxAge = TimeSpan.FromDays(365);
- options.CacheHashLength = 8;
- options.OnParseCommandsAsync = _ => Task.CompletedTask;
- options.OnBeforeSaveAsync = _ => Task.CompletedTask;
- options.OnProcessedAsync = _ => Task.CompletedTask;
- options.OnPrepareResponseAsync = _ => Task.CompletedTask;
- });
- }
+ options.Configuration = Configuration.Default;
+ options.BrowserMaxAge = TimeSpan.FromDays(7);
+ options.CacheMaxAge = TimeSpan.FromDays(365);
+ options.CacheHashLength = 8;
+ options.OnParseCommandsAsync = _ => Task.CompletedTask;
+ options.OnBeforeSaveAsync = _ => Task.CompletedTask;
+ options.OnProcessedAsync = _ => Task.CompletedTask;
+ options.OnPrepareResponseAsync = _ => Task.CompletedTask;
+ });
+ }
- private void ConfigureCustomServicesAndDefaultOptions(IServiceCollection services)
- {
- services.AddImageSharp()
- .RemoveProcessor()
- .RemoveProcessor();
- }
+ private void ConfigureCustomServicesAndDefaultOptions(IServiceCollection services)
+ {
+ services.AddImageSharp()
+ .RemoveProcessor()
+ .RemoveProcessor();
+ }
- private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services)
+ private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services)
+ {
+ services.AddImageSharp(options =>
{
- services.AddImageSharp(options =>
+ options.Configuration = Configuration.Default;
+ options.BrowserMaxAge = TimeSpan.FromDays(7);
+ options.CacheMaxAge = TimeSpan.FromDays(365);
+ options.CacheHashLength = 8;
+ options.OnParseCommandsAsync = _ => Task.CompletedTask;
+ options.OnBeforeSaveAsync = _ => Task.CompletedTask;
+ options.OnProcessedAsync = _ => Task.CompletedTask;
+ options.OnPrepareResponseAsync = _ => Task.CompletedTask;
+ })
+ .SetRequestParser()
+ .Configure(options =>
{
- options.Configuration = Configuration.Default;
- options.BrowserMaxAge = TimeSpan.FromDays(7);
- options.CacheMaxAge = TimeSpan.FromDays(365);
- options.CacheHashLength = 8;
- options.OnParseCommandsAsync = _ => Task.CompletedTask;
- options.OnBeforeSaveAsync = _ => Task.CompletedTask;
- options.OnProcessedAsync = _ => Task.CompletedTask;
- options.OnPrepareResponseAsync = _ => Task.CompletedTask;
+ options.CacheFolder = "different-cache";
})
- .SetRequestParser()
- .Configure(options =>
- {
- options.CacheFolder = "different-cache";
- })
- .SetCache()
- .SetCacheKey()
- .SetCacheHash()
- .ClearProviders()
- .AddProvider()
- .ClearProcessors()
- .AddProcessor()
- .AddProcessor()
- .AddProcessor()
- .AddProcessor();
- }
+ .SetCache()
+ .SetCacheKey()
+ .SetCacheHash()
+ .ClearProviders()
+ .AddProvider()
+ .ClearProcessors()
+ .AddProcessor()
+ .AddProcessor()
+ .AddProcessor()
+ .AddProcessor();
+ }
- ///
- /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- ///
- /// The application builder.
- /// The hosting environment the application is running in.
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ ///
+ /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ ///
+ /// The application builder.
+ /// The hosting environment the application is running in.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
{
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
-
- app.UseDefaultFiles();
- app.UseImageSharp();
- app.UseStaticFiles();
+ app.UseDeveloperExceptionPage();
}
+
+ app.UseDefaultFiles();
+ app.UseImageSharp();
+ app.UseStaticFiles();
}
}
diff --git a/shared-infrastructure b/shared-infrastructure
index f351ed4d..6c52486c 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit f351ed4dbcf7e17c297caa715e51c7659edc528a
+Subproject commit 6c52486c512357475cbb92bbb7c4c0af4d85b1db
diff --git a/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs b/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs
index 98a683dc..772cc42d 100644
--- a/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs
+++ b/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs
@@ -1,63 +1,62 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
-namespace SixLabors.ImageSharp.Web
+namespace SixLabors.ImageSharp.Web;
+
+internal static class AmazonS3ClientFactory
{
- internal static class AmazonS3ClientFactory
+ ///
+ /// Creates a new bucket under the specified account if a bucket
+ /// with the same name does not already exist.
+ ///
+ /// The AWS S3 Storage cache options.
+ ///
+ /// A new .
+ ///
+ public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options)
{
- ///
- /// Creates a new bucket under the specified account if a bucket
- /// with the same name does not already exist.
- ///
- /// The AWS S3 Storage cache options.
- ///
- /// A new .
- ///
- public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options)
+ if (!string.IsNullOrWhiteSpace(options.Endpoint))
+ {
+ // AccessKey can be empty.
+ // AccessSecret can be empty.
+ // PathStyle endpoint doesn't support AccelerateEndpoint.
+ AmazonS3Config config = new() { ServiceURL = options.Endpoint, ForcePathStyle = true, AuthenticationRegion = options.Region };
+ SetTimeout(config, options.Timeout);
+ return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
+ }
+ else if (!string.IsNullOrWhiteSpace(options.AccessKey))
+ {
+ // AccessSecret can be empty.
+ Guard.NotNullOrWhiteSpace(options.Region, nameof(options.Region));
+ var region = RegionEndpoint.GetBySystemName(options.Region);
+ AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint };
+ SetTimeout(config, options.Timeout);
+ return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
+ }
+ else if (!string.IsNullOrWhiteSpace(options.Region))
{
- if (!string.IsNullOrWhiteSpace(options.Endpoint))
- {
- // AccessKey can be empty.
- // AccessSecret can be empty.
- // PathStyle endpoint doesn't support AccelerateEndpoint.
- AmazonS3Config config = new() { ServiceURL = options.Endpoint, ForcePathStyle = true, AuthenticationRegion = options.Region };
- SetTimeout(config, options.Timeout);
- return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
- }
- else if (!string.IsNullOrWhiteSpace(options.AccessKey))
- {
- // AccessSecret can be empty.
- Guard.NotNullOrWhiteSpace(options.Region, nameof(options.Region));
- var region = RegionEndpoint.GetBySystemName(options.Region);
- AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint };
- SetTimeout(config, options.Timeout);
- return new AmazonS3Client(options.AccessKey, options.AccessSecret, config);
- }
- else if (!string.IsNullOrWhiteSpace(options.Region))
- {
- var region = RegionEndpoint.GetBySystemName(options.Region);
- AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint };
- SetTimeout(config, options.Timeout);
- return new AmazonS3Client(config);
- }
- else
- {
- throw new ArgumentException("Invalid configuration.", nameof(options));
- }
+ var region = RegionEndpoint.GetBySystemName(options.Region);
+ AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint };
+ SetTimeout(config, options.Timeout);
+ return new AmazonS3Client(config);
}
+ else
+ {
+ throw new ArgumentException("Invalid configuration.", nameof(options));
+ }
+ }
- private static void SetTimeout(ClientConfig config, TimeSpan? timeout)
+ private static void SetTimeout(ClientConfig config, TimeSpan? timeout)
+ {
+ // We don't want to override the default timeout if it's not set.
+ if (timeout.HasValue)
{
- // We don't want to override the default timeout if it's not set.
- if (timeout.HasValue)
- {
- config.Timeout = timeout.Value;
- }
+ config.Timeout = timeout.Value;
}
}
}
diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs
index b904c86d..c3d2644a 100644
--- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs
+++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs
@@ -1,174 +1,169 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp.Web.Resolvers;
using SixLabors.ImageSharp.Web.Resolvers.AWS;
-namespace SixLabors.ImageSharp.Web.Caching.AWS
+namespace SixLabors.ImageSharp.Web.Caching.AWS;
+
+///
+/// Implements an AWS S3 Storage based cache.
+///
+public class AWSS3StorageCache : IImageCache
{
+ private readonly IAmazonS3 amazonS3Client;
+ private readonly string bucket;
+
///
- /// Implements an AWS S3 Storage based cache.
+ /// Initializes a new instance of the class.
///
- public class AWSS3StorageCache : IImageCache
+ /// The cache options.
+ public AWSS3StorageCache(IOptions cacheOptions)
{
- private readonly IAmazonS3 amazonS3Client;
- private readonly string bucket;
+ Guard.NotNull(cacheOptions, nameof(cacheOptions));
+ AWSS3StorageCacheOptions options = cacheOptions.Value;
+ this.bucket = options.BucketName;
+ this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options);
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The cache options.
- public AWSS3StorageCache(IOptions cacheOptions)
+ ///
+ public async Task GetAsync(string key)
+ {
+ GetObjectMetadataRequest request = new() { BucketName = this.bucket, Key = key };
+ try
{
- Guard.NotNull(cacheOptions, nameof(cacheOptions));
- AWSS3StorageCacheOptions options = cacheOptions.Value;
- this.bucket = options.BucketName;
- this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options);
+ // HEAD request throws a 404 if not found.
+ MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata;
+ return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucket, key, metadata);
}
+ catch
+ {
+ return null;
+ }
+ }
- ///
- public async Task GetAsync(string key)
+ ///
+ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
+ {
+ PutObjectRequest request = new()
{
- GetObjectMetadataRequest request = new() { BucketName = this.bucket, Key = key };
- try
- {
- // HEAD request throws a 404 if not found.
- MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata;
- return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucket, key, metadata);
- }
- catch
+ BucketName = this.bucket,
+ Key = key,
+ ContentType = metadata.ContentType,
+ InputStream = stream,
+ AutoCloseStream = false
+ };
+
+ foreach (KeyValuePair d in metadata.ToDictionary())
+ {
+ request.Metadata.Add(d.Key, d.Value);
+ }
+
+ return this.amazonS3Client.PutObjectAsync(request);
+ }
+
+ ///
+ /// Creates a new bucket under the specified account if a bucket
+ /// with the same name does not already exist.
+ ///
+ /// The AWS S3 Storage cache options.
+ ///
+ /// Specifies whether data in the bucket may be accessed publicly and the level of access.
+ /// specifies full public read access for bucket
+ /// and object data. specifies that the bucket
+ /// data is private to the account owner.
+ ///
+ ///
+ /// If the bucket does not already exist, a describing the newly
+ /// created bucket. If the container already exists, .
+ ///
+ public static PutBucketResponse CreateIfNotExists(
+ AWSS3StorageCacheOptions options,
+ S3CannedACL acl)
+ => AsyncHelper.RunSync(() => CreateIfNotExistsAsync(options, acl));
+
+ private static async Task CreateIfNotExistsAsync(
+ AWSS3StorageCacheOptions options,
+ S3CannedACL acl)
+ {
+ AmazonS3Client client = AmazonS3ClientFactory.CreateClient(options);
+
+ bool foundBucket = false;
+ ListBucketsResponse listBucketsResponse = await client.ListBucketsAsync();
+ foreach (S3Bucket b in listBucketsResponse.Buckets)
+ {
+ if (b.BucketName == options.BucketName)
{
- return null;
+ foundBucket = true;
+ break;
}
}
- ///
- public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
+ if (!foundBucket)
{
- var request = new PutObjectRequest()
+ PutBucketRequest putBucketRequest = new()
{
- BucketName = this.bucket,
- Key = key,
- ContentType = metadata.ContentType,
- InputStream = stream,
- AutoCloseStream = false
+ BucketName = options.BucketName,
+ BucketRegion = options.Region,
+ CannedACL = acl
};
- foreach (KeyValuePair d in metadata.ToDictionary())
- {
- request.Metadata.Add(d.Key, d.Value);
- }
-
- return this.amazonS3Client.PutObjectAsync(request);
+ return await client.PutBucketAsync(putBucketRequest);
}
+ return null;
+ }
+
+ ///
+ ///
+ ///
+ private static class AsyncHelper
+ {
+ private static readonly TaskFactory TaskFactory
+ = new(
+ CancellationToken.None,
+ TaskCreationOptions.None,
+ TaskContinuationOptions.None,
+ TaskScheduler.Default);
+
///
- /// Creates a new bucket under the specified account if a bucket
- /// with the same name does not already exist.
+ /// Executes an async method synchronously.
///
- /// The AWS S3 Storage cache options.
- ///
- /// Specifies whether data in the bucket may be accessed publicly and the level of access.
- /// specifies full public read access for bucket
- /// and object data. specifies that the bucket
- /// data is private to the account owner.
- ///
- ///
- /// If the bucket does not already exist, a describing the newly
- /// created bucket. If the container already exists, .
- ///
- public static PutBucketResponse CreateIfNotExists(
- AWSS3StorageCacheOptions options,
- S3CannedACL acl)
- => AsyncHelper.RunSync(() => CreateIfNotExistsAsync(options, acl));
-
- private static async Task CreateIfNotExistsAsync(
- AWSS3StorageCacheOptions options,
- S3CannedACL acl)
+ /// The task to excecute.
+ public static void RunSync(Func task)
{
- AmazonS3Client client = AmazonS3ClientFactory.CreateClient(options);
-
- bool foundBucket = false;
- ListBucketsResponse listBucketsResponse = await client.ListBucketsAsync();
- foreach (S3Bucket b in listBucketsResponse.Buckets)
+ CultureInfo cultureUi = CultureInfo.CurrentUICulture;
+ CultureInfo culture = CultureInfo.CurrentCulture;
+ TaskFactory.StartNew(() =>
{
- if (b.BucketName == options.BucketName)
- {
- foundBucket = true;
- break;
- }
- }
-
- if (!foundBucket)
- {
- var putBucketRequest = new PutBucketRequest
- {
- BucketName = options.BucketName,
- BucketRegion = options.Region,
- CannedACL = acl
- };
-
- return await client.PutBucketAsync(putBucketRequest);
- }
-
- return null;
+ Thread.CurrentThread.CurrentCulture = culture;
+ Thread.CurrentThread.CurrentUICulture = cultureUi;
+ return task();
+ }).Unwrap().GetAwaiter().GetResult();
}
///
- ///
+ /// Executes an async method which has
+ /// a return type synchronously.
///
- private static class AsyncHelper
+ /// The type of result to return.
+ /// The task to excecute.
+ /// The .
+ public static TResult RunSync(Func> task)
{
- private static readonly TaskFactory TaskFactory
- = new(
- CancellationToken.None,
- TaskCreationOptions.None,
- TaskContinuationOptions.None,
- TaskScheduler.Default);
-
- ///
- /// Executes an async method synchronously.
- ///
- /// The task to excecute.
- public static void RunSync(Func task)
+ CultureInfo cultureUi = CultureInfo.CurrentUICulture;
+ CultureInfo culture = CultureInfo.CurrentCulture;
+ return TaskFactory.StartNew(() =>
{
- CultureInfo cultureUi = CultureInfo.CurrentUICulture;
- CultureInfo culture = CultureInfo.CurrentCulture;
- TaskFactory.StartNew(() =>
- {
- Thread.CurrentThread.CurrentCulture = culture;
- Thread.CurrentThread.CurrentUICulture = cultureUi;
- return task();
- }).Unwrap().GetAwaiter().GetResult();
- }
-
- ///
- /// Executes an async method which has
- /// a return type synchronously.
- ///
- /// The type of result to return.
- /// The task to excecute.
- /// The .
- public static TResult RunSync(Func> task)
- {
- CultureInfo cultureUi = CultureInfo.CurrentUICulture;
- CultureInfo culture = CultureInfo.CurrentCulture;
- return TaskFactory.StartNew(() =>
- {
- Thread.CurrentThread.CurrentCulture = culture;
- Thread.CurrentThread.CurrentUICulture = cultureUi;
- return task();
- }).Unwrap().GetAwaiter().GetResult();
- }
+ Thread.CurrentThread.CurrentCulture = culture;
+ Thread.CurrentThread.CurrentUICulture = cultureUi;
+ return task();
+ }).Unwrap().GetAwaiter().GetResult();
}
}
}
diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs
index 865f4c00..f2845385 100644
--- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs
+++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs
@@ -1,33 +1,32 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-using System;
+// Licensed under the Six Labors Split License.
+#nullable disable
-namespace SixLabors.ImageSharp.Web.Caching.AWS
+namespace SixLabors.ImageSharp.Web.Caching.AWS;
+
+///
+/// Configuration options for the provider.
+///
+public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
{
- ///
- /// Configuration options for the provider.
- ///
- public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
- {
- ///
- public string Region { get; set; }
+ ///
+ public string Region { get; set; }
- ///
- public string BucketName { get; set; }
+ ///
+ public string BucketName { get; set; }
- ///
- public string AccessKey { get; set; }
+ ///
+ public string AccessKey { get; set; }
- ///
- public string AccessSecret { get; set; }
+ ///
+ public string AccessSecret { get; set; }
- ///
- public string Endpoint { get; set; }
+ ///
+ public string Endpoint { get; set; }
- ///
- public bool UseAccelerateEndpoint { get; set; }
+ ///
+ public bool UseAccelerateEndpoint { get; set; }
- ///
- public TimeSpan? Timeout { get; set; }
- }
+ ///
+ public TimeSpan? Timeout { get; set; }
}
diff --git a/src/ImageSharp.Web.Providers.AWS/IAWSS3BucketClientOptions.cs b/src/ImageSharp.Web.Providers.AWS/IAWSS3BucketClientOptions.cs
index 6ef40a40..6a1f285f 100644
--- a/src/ImageSharp.Web.Providers.AWS/IAWSS3BucketClientOptions.cs
+++ b/src/ImageSharp.Web.Providers.AWS/IAWSS3BucketClientOptions.cs
@@ -1,55 +1,53 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
+namespace SixLabors.ImageSharp.Web;
-namespace SixLabors.ImageSharp.Web
+///
+/// Provides a common interface for AWS S3 Bucket Client Options.
+///
+internal interface IAWSS3BucketClientOptions
{
///
- /// Provides a common interface for AWS S3 Bucket Client Options.
+ /// Gets or sets the AWS region endpoint (us-east-1/us-west-1/ap-southeast-2).
///
- internal interface IAWSS3BucketClientOptions
- {
- ///
- /// Gets or sets the AWS region endpoint (us-east-1/us-west-1/ap-southeast-2).
- ///
- string Region { get; set; }
-
- ///
- /// Gets or sets the AWS bucket name.
- ///
- string BucketName { get; set; }
-
- ///
- /// Gets or sets the AWS key - Can be used to override keys provided by the environment.
- /// If deploying inside an EC2 instance AWS keys will already be available via environment
- /// variables and don't need to be specified. Follow AWS best security practices on .
- ///
- string AccessKey { get; set; }
-
- ///
- /// Gets or sets the AWS secret - Can be used to override keys provided by the environment.
- /// If deploying inside an EC2 instance AWS keys will already be available via environment
- /// variables and don't need to be specified. Follow AWS best security practices on .
- ///
- string AccessSecret { get; set; }
-
- ///
- /// Gets or sets the AWS endpoint - used for testing to over region endpoint allowing it
- /// to be set to localhost.
- ///
- string Endpoint { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the S3 accelerate endpoint is used.
- /// The feature must be enabled on the bucket. Follow AWS instruction on .
- ///
- bool UseAccelerateEndpoint { get; set; }
-
- ///
- /// Gets or sets a value indicating the timeout for the S3 client.
- /// If the value is set, the value is assigned to the Timeout property of the HttpWebRequest/HttpClient object used to send requests.
- ///
- TimeSpan? Timeout { get; set; }
- }
+ string Region { get; set; }
+
+ ///
+ /// Gets or sets the AWS bucket name.
+ ///
+ string BucketName { get; set; }
+
+ ///
+ /// Gets or sets the AWS key - Can be used to override keys provided by the environment.
+ /// If deploying inside an EC2 instance AWS keys will already be available via environment
+ /// variables and don't need to be specified. Follow AWS best security practices on .
+ ///
+ string AccessKey { get; set; }
+
+ ///
+ /// Gets or sets the AWS secret - Can be used to override keys provided by the environment.
+ /// If deploying inside an EC2 instance AWS keys will already be available via environment
+ /// variables and don't need to be specified. Follow AWS best security practices on .
+ ///
+ string AccessSecret { get; set; }
+
+ ///
+ /// Gets or sets the AWS endpoint - used for testing to over region endpoint allowing it
+ /// to be set to localhost.
+ ///
+ string Endpoint { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the S3 accelerate endpoint is used.
+ /// The feature must be enabled on the bucket. Follow AWS instruction on .
+ ///
+ bool UseAccelerateEndpoint { get; set; }
+
+ ///
+ /// Gets or sets a value indicating the timeout for the S3 client.
+ /// If the value is set, the value is assigned to the Timeout property of the HttpWebRequest/HttpClient object used to send requests.
+ ///
+ TimeSpan? Timeout { get; set; }
}
diff --git a/src/ImageSharp.Web.Providers.AWS/ImageSharp.Web.Providers.AWS.csproj b/src/ImageSharp.Web.Providers.AWS/ImageSharp.Web.Providers.AWS.csproj
index 1e1a6f11..4cf1f6b1 100644
--- a/src/ImageSharp.Web.Providers.AWS/ImageSharp.Web.Providers.AWS.csproj
+++ b/src/ImageSharp.Web.Providers.AWS/ImageSharp.Web.Providers.AWS.csproj
@@ -6,27 +6,39 @@
SixLabors.ImageSharp.WebSixLabors.ImageSharp.Web.Providers.AWSsixlabors.imagesharp.web.128.png
- Apache-2.0
+ LICENSEhttps://github.com/SixLabors/ImageSharp.Web/$(RepositoryUrl)Image Middleware Resize Crop Gif Jpg Jpeg Bitmap Png AWSA provider for resolving and caching images via AWS S3 Storage.
+
+
+ enable
+ Nullable
+
+
+
+
+ 3.0
+
+
- net6.0;netcoreapp3.1
+ net7.0;net6.0
- netcoreapp3.1
+ net6.0
+
diff --git a/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProvider.cs b/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProvider.cs
index a32389cd..dec40df1 100644
--- a/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProvider.cs
+++ b/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProvider.cs
@@ -1,9 +1,7 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.AspNetCore.Http;
@@ -12,163 +10,162 @@
using SixLabors.ImageSharp.Web.Resolvers;
using SixLabors.ImageSharp.Web.Resolvers.AWS;
-namespace SixLabors.ImageSharp.Web.Providers.AWS
+namespace SixLabors.ImageSharp.Web.Providers.AWS;
+
+///
+/// Returns images stored in AWS S3.
+///
+public class AWSS3StorageImageProvider : IImageProvider
{
///
- /// Returns images stored in AWS S3.
+ /// Character array to remove from paths.
+ ///
+ private static readonly char[] SlashChars = { '\\', '/' };
+
+ ///
+ /// The containers for the blob services.
+ ///
+ private readonly Dictionary buckets
+ = new();
+
+ private readonly AWSS3StorageImageProviderOptions storageOptions;
+ private Func match;
+
+ ///
+ /// Contains various helper methods based on the current configuration.
+ ///
+ private readonly FormatUtilities formatUtilities;
+
+ ///
+ /// Initializes a new instance of the class.
///
- public class AWSS3StorageImageProvider : IImageProvider
+ /// The S3 storage options
+ /// Contains various format helper methods based on the current configuration.
+ public AWSS3StorageImageProvider(IOptions storageOptions, FormatUtilities formatUtilities)
{
- ///
- /// Character array to remove from paths.
- ///
- private static readonly char[] SlashChars = { '\\', '/' };
-
- ///
- /// The containers for the blob services.
- ///
- private readonly Dictionary buckets
- = new();
-
- private readonly AWSS3StorageImageProviderOptions storageOptions;
- private Func match;
-
- ///
- /// Contains various helper methods based on the current configuration.
- ///
- private readonly FormatUtilities formatUtilities;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The S3 storage options
- /// Contains various format helper methods based on the current configuration.
- public AWSS3StorageImageProvider(IOptions storageOptions, FormatUtilities formatUtilities)
+ Guard.NotNull(storageOptions, nameof(storageOptions));
+
+ this.storageOptions = storageOptions.Value;
+
+ this.formatUtilities = formatUtilities;
+
+ foreach (AWSS3BucketClientOptions bucket in this.storageOptions.S3Buckets)
{
- Guard.NotNull(storageOptions, nameof(storageOptions));
+ this.buckets.Add(bucket.BucketName, AmazonS3ClientFactory.CreateClient(bucket));
+ }
+ }
- this.storageOptions = storageOptions.Value;
+ ///
+ public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.All;
- this.formatUtilities = formatUtilities;
+ ///
+ public Func Match
+ {
+ get => this.match ?? this.IsMatch;
+ set => this.match = value;
+ }
- foreach (AWSS3BucketClientOptions bucket in this.storageOptions.S3Buckets)
+ ///
+ public bool IsValidRequest(HttpContext context)
+ => this.formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _);
+
+ ///
+ public async Task GetAsync(HttpContext context)
+ {
+ // Strip the leading slash and bucket name from the HTTP request path and treat
+ // the remaining path string as the key.
+ // Path has already been correctly parsed before here.
+ string bucketName = string.Empty;
+ IAmazonS3 s3Client = null;
+
+ // We want an exact match here to ensure that bucket names starting with
+ // the same prefix are not mixed up.
+ string path = context.Request.Path.Value.TrimStart(SlashChars);
+ int index = path.IndexOfAny(SlashChars);
+ string nameToMatch = index != -1 ? path.Substring(0, index) : path;
+
+ foreach (string k in this.buckets.Keys)
+ {
+ if (nameToMatch.Equals(k, StringComparison.OrdinalIgnoreCase))
{
- this.buckets.Add(bucket.BucketName, AmazonS3ClientFactory.CreateClient(bucket));
+ bucketName = k;
+ s3Client = this.buckets[k];
+ break;
}
}
- ///
- public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.All;
-
- ///
- public Func Match
+ // Something has gone horribly wrong for this to happen but check anyway.
+ if (s3Client is null)
{
- get => this.match ?? this.IsMatch;
- set => this.match = value;
+ return null;
}
- ///
- public bool IsValidRequest(HttpContext context)
- => this.formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _);
+ // Key should be the remaining path string.
+ string key = path.Substring(bucketName.Length).TrimStart(SlashChars);
- ///
- public async Task GetAsync(HttpContext context)
+ if (string.IsNullOrWhiteSpace(key))
{
- // Strip the leading slash and bucket name from the HTTP request path and treat
- // the remaining path string as the key.
- // Path has already been correctly parsed before here.
- string bucketName = string.Empty;
- IAmazonS3 s3Client = null;
-
- // We want an exact match here to ensure that bucket names starting with
- // the same prefix are not mixed up.
- string path = context.Request.Path.Value.TrimStart(SlashChars);
- int index = path.IndexOfAny(SlashChars);
- string nameToMatch = index != -1 ? path.Substring(0, index) : path;
-
- foreach (string k in this.buckets.Keys)
- {
- if (nameToMatch.Equals(k, StringComparison.OrdinalIgnoreCase))
- {
- bucketName = k;
- s3Client = this.buckets[k];
- break;
- }
- }
+ return null;
+ }
- // Something has gone horribly wrong for this to happen but check anyway.
- if (s3Client is null)
- {
- return null;
- }
+ if (!await KeyExists(s3Client, bucketName, key))
+ {
+ return null;
+ }
- // Key should be the remaining path string.
- string key = path.Substring(bucketName.Length).TrimStart(SlashChars);
+ return new AWSS3StorageImageResolver(s3Client, bucketName, key);
+ }
- if (string.IsNullOrWhiteSpace(key))
+ private bool IsMatch(HttpContext context)
+ {
+ // Only match loosly here for performance.
+ // Path matching conflicts should be dealt with by configuration.
+ string path = context.Request.Path.Value.TrimStart(SlashChars);
+ foreach (string bucket in this.buckets.Keys)
+ {
+ if (path.StartsWith(bucket, StringComparison.OrdinalIgnoreCase))
{
- return null;
+ return true;
}
+ }
- if (!await this.KeyExists(s3Client, bucketName, key))
+ return false;
+ }
+
+ // ref https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Services/S3/Custom/_bcl/IO/S3FileInfo.cs#L118
+ private static async Task KeyExists(IAmazonS3 s3Client, string bucketName, string key)
+ {
+ try
+ {
+ GetObjectMetadataRequest request = new()
{
- return null;
- }
+ BucketName = bucketName,
+ Key = key
+ };
- return new AWSS3StorageImageResolver(s3Client, bucketName, key);
+ // If the object doesn't exist then a "NotFound" will be thrown
+ await s3Client.GetObjectMetadataAsync(request);
+ return true;
}
-
- private bool IsMatch(HttpContext context)
+ catch (AmazonS3Exception e)
{
- // Only match loosly here for performance.
- // Path matching conflicts should be dealt with by configuration.
- string path = context.Request.Path.Value.TrimStart(SlashChars);
- foreach (string bucket in this.buckets.Keys)
+ if (string.Equals(e.ErrorCode, "NoSuchBucket", StringComparison.Ordinal))
{
- if (path.StartsWith(bucket, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
+ return false;
}
- return false;
- }
-
- // ref https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Services/S3/Custom/_bcl/IO/S3FileInfo.cs#L118
- private async Task KeyExists(IAmazonS3 s3Client, string bucketName, string key)
- {
- try
+ if (string.Equals(e.ErrorCode, "NotFound", StringComparison.Ordinal))
{
- var request = new GetObjectMetadataRequest
- {
- BucketName = bucketName,
- Key = key
- };
-
- // If the object doesn't exist then a "NotFound" will be thrown
- await s3Client.GetObjectMetadataAsync(request);
- return true;
+ return false;
}
- catch (AmazonS3Exception e)
+
+ // If the object exists but the client is not authorized to access it, then a "Forbidden" will be thrown.
+ if (string.Equals(e.ErrorCode, "Forbidden", StringComparison.Ordinal))
{
- if (string.Equals(e.ErrorCode, "NoSuchBucket"))
- {
- return false;
- }
-
- if (string.Equals(e.ErrorCode, "NotFound"))
- {
- return false;
- }
-
- // If the object exists but the client is not authorized to access it, then a "Forbidden" will be thrown.
- if (string.Equals(e.ErrorCode, "Forbidden"))
- {
- return false;
- }
-
- throw;
+ return false;
}
+
+ throw;
}
}
}
diff --git a/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProviderOptions.cs b/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProviderOptions.cs
index 6fcf4f06..14eac3e6 100644
--- a/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProviderOptions.cs
+++ b/src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProviderOptions.cs
@@ -1,46 +1,43 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.Collections.Generic;
+namespace SixLabors.ImageSharp.Web.Providers.AWS;
-namespace SixLabors.ImageSharp.Web.Providers.AWS
+///
+/// Configuration options for the provider.
+///
+public class AWSS3StorageImageProviderOptions
{
///
- /// Configuration options for the provider.
+ /// Gets or sets the collection of blob container client options.
///
- public class AWSS3StorageImageProviderOptions
- {
- ///
- /// Gets or sets the collection of blob container client options.
- ///
- public ICollection S3Buckets { get; set; } = new HashSet();
- }
+ public ICollection S3Buckets { get; set; } = new HashSet();
+}
- ///
- /// Configuration options for the provider.
- ///
- public class AWSS3BucketClientOptions : IAWSS3BucketClientOptions
- {
- ///
- public string Region { get; set; }
+///
+/// Configuration options for the provider.
+///
+public class AWSS3BucketClientOptions : IAWSS3BucketClientOptions
+{
+ ///
+ public string Region { get; set; }
- ///
- public string BucketName { get; set; }
+ ///
+ public string BucketName { get; set; }
- ///
- public string AccessKey { get; set; }
+ ///
+ public string AccessKey { get; set; }
- ///
- public string AccessSecret { get; set; }
+ ///
+ public string AccessSecret { get; set; }
- ///
- public string Endpoint { get; set; }
+ ///
+ public string Endpoint { get; set; }
- ///
- public bool UseAccelerateEndpoint { get; set; }
+ ///
+ public bool UseAccelerateEndpoint { get; set; }
- ///
- public TimeSpan? Timeout { get; set; }
- }
+ ///
+ public TimeSpan? Timeout { get; set; }
}
diff --git a/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageCacheResolver.cs b/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageCacheResolver.cs
index 66097233..7fbf750e 100644
--- a/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageCacheResolver.cs
+++ b/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageCacheResolver.cs
@@ -1,53 +1,50 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
-namespace SixLabors.ImageSharp.Web.Resolvers.AWS
+namespace SixLabors.ImageSharp.Web.Resolvers.AWS;
+
+///
+/// Provides means to manage image buffers within the .
+///
+public class AWSS3StorageCacheResolver : IImageCacheResolver
{
+ private readonly IAmazonS3 amazonS3;
+ private readonly string bucketName;
+ private readonly string imagePath;
+ private readonly MetadataCollection metadata;
+
///
- /// Provides means to manage image buffers within the .
+ /// Initializes a new instance of the class.
///
- public class AWSS3StorageCacheResolver : IImageCacheResolver
+ /// The Amazon S3 Client
+ /// The bucket name.
+ /// The image path.
+ /// The metadata collection.
+ public AWSS3StorageCacheResolver(IAmazonS3 amazonS3, string bucketName, string imagePath, MetadataCollection metadata)
{
- private readonly IAmazonS3 amazonS3;
- private readonly string bucketName;
- private readonly string imagePath;
- private readonly MetadataCollection metadata;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The Amazon S3 Client
- /// The bucket name.
- /// The image path.
- /// The metadata collection.
- public AWSS3StorageCacheResolver(IAmazonS3 amazonS3, string bucketName, string imagePath, MetadataCollection metadata)
- {
- this.amazonS3 = amazonS3;
- this.bucketName = bucketName;
- this.imagePath = imagePath;
- this.metadata = metadata;
- }
+ this.amazonS3 = amazonS3;
+ this.bucketName = bucketName;
+ this.imagePath = imagePath;
+ this.metadata = metadata;
+ }
- ///
- public Task GetMetaDataAsync()
+ ///
+ public Task GetMetaDataAsync()
+ {
+ Dictionary dict = new();
+ foreach (string key in this.metadata.Keys)
{
- Dictionary dict = new();
- foreach (string key in this.metadata.Keys)
- {
- // Trim automatically added x-amz-meta-
- dict.Add(key.Substring(11).ToUpperInvariant(), this.metadata[key]);
- }
-
- return Task.FromResult(ImageCacheMetadata.FromDictionary(dict));
+ // Trim automatically added x-amz-meta-
+ dict.Add(key.Substring(11).ToUpperInvariant(), this.metadata[key]);
}
- ///
- public Task OpenReadAsync() => this.amazonS3.GetObjectStreamAsync(this.bucketName, this.imagePath, null);
+ return Task.FromResult(ImageCacheMetadata.FromDictionary(dict));
}
+
+ ///
+ public Task OpenReadAsync() => this.amazonS3.GetObjectStreamAsync(this.bucketName, this.imagePath, null);
}
diff --git a/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageImageResolver.cs b/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageImageResolver.cs
index bbb47cdf..f58930ed 100644
--- a/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageImageResolver.cs
+++ b/src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageImageResolver.cs
@@ -1,58 +1,55 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.IO;
using System.Net.Http.Headers;
-using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
-namespace SixLabors.ImageSharp.Web.Resolvers.AWS
+namespace SixLabors.ImageSharp.Web.Resolvers.AWS;
+
+///
+/// Provides means to manage image buffers within the AWS S3 file system.
+///
+public class AWSS3StorageImageResolver : IImageResolver
{
+ private readonly IAmazonS3 amazonS3;
+ private readonly string bucketName;
+ private readonly string imagePath;
+
///
- /// Provides means to manage image buffers within the AWS S3 file system.
+ /// Initializes a new instance of the class.
///
- public class AWSS3StorageImageResolver : IImageResolver
+ /// The Amazon S3 Client
+ /// The bucket name.
+ /// The image path.
+ public AWSS3StorageImageResolver(IAmazonS3 amazonS3, string bucketName, string imagePath)
{
- private readonly IAmazonS3 amazonS3;
- private readonly string bucketName;
- private readonly string imagePath;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The Amazon S3 Client
- /// The bucket name.
- /// The image path.
- public AWSS3StorageImageResolver(IAmazonS3 amazonS3, string bucketName, string imagePath)
- {
- this.amazonS3 = amazonS3;
- this.bucketName = bucketName;
- this.imagePath = imagePath;
- }
+ this.amazonS3 = amazonS3;
+ this.bucketName = bucketName;
+ this.imagePath = imagePath;
+ }
- ///
- public async Task GetMetaDataAsync()
- {
- GetObjectMetadataResponse metadata = await this.amazonS3.GetObjectMetadataAsync(this.bucketName, this.imagePath);
+ ///
+ public async Task GetMetaDataAsync()
+ {
+ GetObjectMetadataResponse metadata = await this.amazonS3.GetObjectMetadataAsync(this.bucketName, this.imagePath);
- // Try to parse the max age from the source. If it's not zero then we pass it along
- // to set the cache control headers for the response.
- TimeSpan maxAge = TimeSpan.MinValue;
- if (CacheControlHeaderValue.TryParse(metadata.Headers.CacheControl, out CacheControlHeaderValue cacheControl))
+ // Try to parse the max age from the source. If it's not zero then we pass it along
+ // to set the cache control headers for the response.
+ TimeSpan maxAge = TimeSpan.MinValue;
+ if (CacheControlHeaderValue.TryParse(metadata.Headers.CacheControl, out CacheControlHeaderValue cacheControl))
+ {
+ // Weirdly passing null to TryParse returns true.
+ if (cacheControl?.MaxAge.HasValue == true)
{
- // Weirdly passing null to TryParse returns true.
- if (cacheControl?.MaxAge.HasValue == true)
- {
- maxAge = cacheControl.MaxAge.Value;
- }
+ maxAge = cacheControl.MaxAge.Value;
}
-
- return new ImageMetadata(metadata.LastModified, maxAge, metadata.ContentLength);
}
- ///
- public Task OpenReadAsync() => this.amazonS3.GetObjectStreamAsync(this.bucketName, this.imagePath, null);
+ return new ImageMetadata(metadata.LastModified, maxAge, metadata.ContentLength);
}
+
+ ///
+ public Task OpenReadAsync() => this.amazonS3.GetObjectStreamAsync(this.bucketName, this.imagePath, null);
}
diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs
index d1224cab..d147b217 100644
--- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs
+++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs
@@ -1,8 +1,7 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System.IO;
-using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
@@ -10,76 +9,75 @@
using SixLabors.ImageSharp.Web.Resolvers;
using SixLabors.ImageSharp.Web.Resolvers.Azure;
-namespace SixLabors.ImageSharp.Web.Caching.Azure
+namespace SixLabors.ImageSharp.Web.Caching.Azure;
+
+///
+/// Implements an Azure Blob Storage based cache.
+///
+public class AzureBlobStorageCache : IImageCache
{
+ private readonly BlobContainerClient container;
+
///
- /// Implements an Azure Blob Storage based cache.
+ /// Initializes a new instance of the class.
///
- public class AzureBlobStorageCache : IImageCache
+ /// The cache options.
+ public AzureBlobStorageCache(IOptions cacheOptions)
{
- private readonly BlobContainerClient container;
+ Guard.NotNull(cacheOptions, nameof(cacheOptions));
+ AzureBlobStorageCacheOptions options = cacheOptions.Value;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The cache options.
- public AzureBlobStorageCache(IOptions cacheOptions)
- {
- Guard.NotNull(cacheOptions, nameof(cacheOptions));
- AzureBlobStorageCacheOptions options = cacheOptions.Value;
+ this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName);
+ }
- this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName);
- }
+ ///
+ public async Task GetAsync(string key)
+ {
+ BlobClient blob = this.container.GetBlobClient(key);
- ///
- public async Task GetAsync(string key)
+ if (!await blob.ExistsAsync())
{
- BlobClient blob = this.container.GetBlobClient(key);
-
- if (!await blob.ExistsAsync())
- {
- return null;
- }
-
- return new AzureBlobStorageCacheResolver(blob);
+ return null;
}
- ///
- public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
- {
- BlobClient blob = this.container.GetBlobClient(key);
+ return new AzureBlobStorageCacheResolver(blob);
+ }
- var headers = new BlobHttpHeaders
- {
- ContentType = metadata.ContentType,
- };
+ ///
+ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
+ {
+ BlobClient blob = this.container.GetBlobClient(key);
- return blob.UploadAsync(stream, httpHeaders: headers, metadata: metadata.ToDictionary());
- }
+ BlobHttpHeaders headers = new()
+ {
+ ContentType = metadata.ContentType,
+ };
- ///
- /// Creates a new container under the specified account if a container
- /// with the same name does not already exist.
- ///
- /// The Azure Blob Storage cache options.
- ///
- /// Optionally specifies whether data in the container may be accessed publicly and
- /// the level of access.
- /// specifies full public read access for container and blob data. Clients can enumerate
- /// blobs within the container via anonymous request, but cannot enumerate containers
- /// within the storage account.
- /// specifies public read access for blobs. Blob data within this container can be
- /// read via anonymous request, but container data is not available. Clients cannot
- /// enumerate blobs within the container via anonymous request.
- /// specifies that the container data is private to the account owner.
- ///
- ///
- /// If the container does not already exist, a describing the newly
- /// created container. If the container already exists, .
- ///
- public static Response CreateIfNotExists(
- AzureBlobStorageCacheOptions options,
- PublicAccessType accessType)
- => new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType);
+ return blob.UploadAsync(stream, httpHeaders: headers, metadata: metadata.ToDictionary());
}
+
+ ///
+ /// Creates a new container under the specified account if a container
+ /// with the same name does not already exist.
+ ///
+ /// The Azure Blob Storage cache options.
+ ///
+ /// Optionally specifies whether data in the container may be accessed publicly and
+ /// the level of access.
+ /// specifies full public read access for container and blob data. Clients can enumerate
+ /// blobs within the container via anonymous request, but cannot enumerate containers
+ /// within the storage account.
+ /// specifies public read access for blobs. Blob data within this container can be
+ /// read via anonymous request, but container data is not available. Clients cannot
+ /// enumerate blobs within the container via anonymous request.
+ /// specifies that the container data is private to the account owner.
+ ///
+ ///
+ /// If the container does not already exist, a describing the newly
+ /// created container. If the container already exists, .
+ ///
+ public static Response CreateIfNotExists(
+ AzureBlobStorageCacheOptions options,
+ PublicAccessType accessType)
+ => new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType);
}
diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs
index adddb58e..640b5fc6 100644
--- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs
+++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs
@@ -1,17 +1,17 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-namespace SixLabors.ImageSharp.Web.Caching.Azure
+namespace SixLabors.ImageSharp.Web.Caching.Azure;
+
+///
+/// Configuration options for the .
+///
+public class AzureBlobStorageCacheOptions : IAzureBlobContainerClientOptions
{
- ///
- /// Configuration options for the .
- ///
- public class AzureBlobStorageCacheOptions : IAzureBlobContainerClientOptions
- {
- ///
- public string ConnectionString { get; set; }
+ ///
+ public string ConnectionString { get; set; }
- ///
- public string ContainerName { get; set; }
- }
+ ///
+ public string ContainerName { get; set; }
}
diff --git a/src/ImageSharp.Web.Providers.Azure/IAzureBlobContainerClientOptions.cs b/src/ImageSharp.Web.Providers.Azure/IAzureBlobContainerClientOptions.cs
index 6048b5e6..b3ee1e1d 100644
--- a/src/ImageSharp.Web.Providers.Azure/IAzureBlobContainerClientOptions.cs
+++ b/src/ImageSharp.Web.Providers.Azure/IAzureBlobContainerClientOptions.cs
@@ -1,24 +1,24 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-namespace SixLabors.ImageSharp.Web
+namespace SixLabors.ImageSharp.Web;
+
+///
+/// Provides a common interface for Azure Blob Container Options.
+///
+internal interface IAzureBlobContainerClientOptions
{
///
- /// Provides a common interface for Azure Blob Container Options.
+ /// Gets or sets the Azure Blob Storage connection string.
+ ///
///
- internal interface IAzureBlobContainerClientOptions
- {
- ///
- /// Gets or sets the Azure Blob Storage connection string.
- ///
- ///
- public string ConnectionString { get; set; }
+ public string ConnectionString { get; set; }
- ///
- /// Gets or sets the Azure Blob Storage container name.
- /// Must conform to Azure Blob Storage container naming guidlines.
- ///
- ///
- public string ContainerName { get; set; }
- }
+ ///
+ /// Gets or sets the Azure Blob Storage container name.
+ /// Must conform to Azure Blob Storage container naming guidlines.
+ ///
+ ///
+ public string ContainerName { get; set; }
}
diff --git a/src/ImageSharp.Web.Providers.Azure/ImageSharp.Web.Providers.Azure.csproj b/src/ImageSharp.Web.Providers.Azure/ImageSharp.Web.Providers.Azure.csproj
index f4ae2f6d..ed03b642 100644
--- a/src/ImageSharp.Web.Providers.Azure/ImageSharp.Web.Providers.Azure.csproj
+++ b/src/ImageSharp.Web.Providers.Azure/ImageSharp.Web.Providers.Azure.csproj
@@ -6,27 +6,39 @@
SixLabors.ImageSharp.WebSixLabors.ImageSharp.Web.Providers.Azuresixlabors.imagesharp.web.128.png
- Apache-2.0
+ LICENSEhttps://github.com/SixLabors/ImageSharp.Web/$(RepositoryUrl)Image Middleware Resize Crop Gif Jpg Jpeg Bitmap Png AzureA provider for resolving and caching images via Azure Blob Storage.
+
+
+ enable
+ Nullable
+
+
+
+
+ 3.0
+
+
- net6.0;netcoreapp3.1
+ net7.0;net6.0
- netcoreapp3.1
+ net6.0
+
diff --git a/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProvider.cs b/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProvider.cs
index 2627ec27..32d4ca19 100644
--- a/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProvider.cs
+++ b/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProvider.cs
@@ -1,9 +1,7 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
@@ -11,138 +9,137 @@
using SixLabors.ImageSharp.Web.Resolvers;
using SixLabors.ImageSharp.Web.Resolvers.Azure;
-namespace SixLabors.ImageSharp.Web.Providers.Azure
+namespace SixLabors.ImageSharp.Web.Providers.Azure;
+
+///
+/// Returns images stored in Azure Blob Storage.
+///
+public class AzureBlobStorageImageProvider : IImageProvider
{
///
- /// Returns images stored in Azure Blob Storage.
+ /// Character array to remove from paths.
///
- public class AzureBlobStorageImageProvider : IImageProvider
- {
- ///
- /// Character array to remove from paths.
- ///
- private static readonly char[] SlashChars = { '\\', '/' };
-
- ///
- /// The containers for the blob services.
- ///
- private readonly Dictionary containers
- = new();
-
- ///
- /// The blob storage options.
- ///
- private readonly AzureBlobStorageImageProviderOptions storageOptions;
-
- ///
- /// Contains various helper methods based on the current configuration.
- ///
- private readonly FormatUtilities formatUtilities;
-
- ///
- /// A match function used by the resolver to identify itself as the correct resolver to use.
- ///
- private Func match;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The blob storage options.
- /// Contains various format helper methods based on the current configuration.
- public AzureBlobStorageImageProvider(
- IOptions storageOptions,
- FormatUtilities formatUtilities)
- {
- Guard.NotNull(storageOptions, nameof(storageOptions));
+ private static readonly char[] SlashChars = { '\\', '/' };
- this.storageOptions = storageOptions.Value;
- this.formatUtilities = formatUtilities;
+ ///
+ /// The containers for the blob services.
+ ///
+ private readonly Dictionary containers
+ = new();
- foreach (AzureBlobContainerClientOptions container in this.storageOptions.BlobContainers)
- {
- this.containers.Add(
- container.ContainerName,
- new BlobContainerClient(container.ConnectionString, container.ContainerName));
- }
- }
+ ///
+ /// The blob storage options.
+ ///
+ private readonly AzureBlobStorageImageProviderOptions storageOptions;
+
+ ///
+ /// Contains various helper methods based on the current configuration.
+ ///
+ private readonly FormatUtilities formatUtilities;
- ///
- public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.All;
+ ///
+ /// A match function used by the resolver to identify itself as the correct resolver to use.
+ ///
+ private Func match;
- ///
- public Func Match
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The blob storage options.
+ /// Contains various format helper methods based on the current configuration.
+ public AzureBlobStorageImageProvider(
+ IOptions storageOptions,
+ FormatUtilities formatUtilities)
+ {
+ Guard.NotNull(storageOptions, nameof(storageOptions));
+
+ this.storageOptions = storageOptions.Value;
+ this.formatUtilities = formatUtilities;
+
+ foreach (AzureBlobContainerClientOptions container in this.storageOptions.BlobContainers)
{
- get => this.match ?? this.IsMatch;
- set => this.match = value;
+ this.containers.Add(
+ container.ContainerName,
+ new BlobContainerClient(container.ConnectionString, container.ContainerName));
}
+ }
- ///
- public async Task GetAsync(HttpContext context)
- {
- // Strip the leading slash and container name from the HTTP request path and treat
- // the remaining path string as the blob name.
- // Path has already been correctly parsed before here.
- string containerName = string.Empty;
- BlobContainerClient container = null;
-
- // We want an exact match here to ensure that container names starting with
- // the same prefix are not mixed up.
- string path = context.Request.Path.Value.TrimStart(SlashChars);
- int index = path.IndexOfAny(SlashChars);
- string nameToMatch = index != -1 ? path.Substring(0, index) : path;
-
- foreach (string key in this.containers.Keys)
- {
- if (nameToMatch.Equals(key, StringComparison.OrdinalIgnoreCase))
- {
- containerName = key;
- container = this.containers[key];
- break;
- }
- }
+ ///
+ public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.All;
+
+ ///
+ public Func Match
+ {
+ get => this.match ?? this.IsMatch;
+ set => this.match = value;
+ }
- // Something has gone horribly wrong for this to happen but check anyway.
- if (container is null)
+ ///
+ public async Task GetAsync(HttpContext context)
+ {
+ // Strip the leading slash and container name from the HTTP request path and treat
+ // the remaining path string as the blob name.
+ // Path has already been correctly parsed before here.
+ string containerName = string.Empty;
+ BlobContainerClient container = null;
+
+ // We want an exact match here to ensure that container names starting with
+ // the same prefix are not mixed up.
+ string path = context.Request.Path.Value.TrimStart(SlashChars);
+ int index = path.IndexOfAny(SlashChars);
+ string nameToMatch = index != -1 ? path.Substring(0, index) : path;
+
+ foreach (string key in this.containers.Keys)
+ {
+ if (nameToMatch.Equals(key, StringComparison.OrdinalIgnoreCase))
{
- return null;
+ containerName = key;
+ container = this.containers[key];
+ break;
}
+ }
- // Blob name should be the remaining path string.
- string blobName = path.Substring(containerName.Length).TrimStart(SlashChars);
+ // Something has gone horribly wrong for this to happen but check anyway.
+ if (container is null)
+ {
+ return null;
+ }
- if (string.IsNullOrWhiteSpace(blobName))
- {
- return null;
- }
+ // Blob name should be the remaining path string.
+ string blobName = path.Substring(containerName.Length).TrimStart(SlashChars);
- BlobClient blob = container.GetBlobClient(blobName);
+ if (string.IsNullOrWhiteSpace(blobName))
+ {
+ return null;
+ }
- if (!await blob.ExistsAsync())
- {
- return null;
- }
+ BlobClient blob = container.GetBlobClient(blobName);
- return new AzureBlobStorageImageResolver(blob);
+ if (!await blob.ExistsAsync())
+ {
+ return null;
}
- ///
- public bool IsValidRequest(HttpContext context)
- => this.formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _);
+ return new AzureBlobStorageImageResolver(blob);
+ }
+
+ ///
+ public bool IsValidRequest(HttpContext context)
+ => this.formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _);
- private bool IsMatch(HttpContext context)
+ private bool IsMatch(HttpContext context)
+ {
+ // Only match loosly here for performance.
+ // Path matching conflicts should be dealt with by configuration.
+ string path = context.Request.Path.Value.TrimStart(SlashChars);
+ foreach (string container in this.containers.Keys)
{
- // Only match loosly here for performance.
- // Path matching conflicts should be dealt with by configuration.
- string path = context.Request.Path.Value.TrimStart(SlashChars);
- foreach (string container in this.containers.Keys)
+ if (path.StartsWith(container, StringComparison.OrdinalIgnoreCase))
{
- if (path.StartsWith(container, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
+ return true;
}
-
- return false;
}
+
+ return false;
}
}
diff --git a/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProviderOptions.cs b/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProviderOptions.cs
index 6afd6538..a5d8e92c 100644
--- a/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProviderOptions.cs
+++ b/src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProviderOptions.cs
@@ -1,30 +1,28 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System.Collections.Generic;
+namespace SixLabors.ImageSharp.Web.Providers.Azure;
-namespace SixLabors.ImageSharp.Web.Providers.Azure
+///
+/// Configuration options for the provider.
+///
+public class AzureBlobStorageImageProviderOptions
{
///
- /// Configuration options for the provider.
+ /// Gets or sets the collection of blob container client options.
///
- public class AzureBlobStorageImageProviderOptions
- {
- ///
- /// Gets or sets the collection of blob container client options.
- ///
- public ICollection BlobContainers { get; set; } = new HashSet();
- }
+ public ICollection BlobContainers { get; set; } = new HashSet();
+}
- ///
- /// Represents a single Azure Blob Storage connection and container.
- ///
- public class AzureBlobContainerClientOptions : IAzureBlobContainerClientOptions
- {
- ///
- public string ConnectionString { get; set; }
+///
+/// Represents a single Azure Blob Storage connection and container.
+///
+public class AzureBlobContainerClientOptions : IAzureBlobContainerClientOptions
+{
+ ///
+ public string ConnectionString { get; set; }
- ///
- public string ContainerName { get; set; }
- }
+ ///
+ public string ContainerName { get; set; }
}
diff --git a/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageCacheResolver.cs b/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageCacheResolver.cs
index 82922bed..708dc899 100644
--- a/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageCacheResolver.cs
+++ b/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageCacheResolver.cs
@@ -1,38 +1,36 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System.IO;
-using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using SixLabors.ImageSharp.Web.Caching.Azure;
-namespace SixLabors.ImageSharp.Web.Resolvers.Azure
+namespace SixLabors.ImageSharp.Web.Resolvers.Azure;
+
+///
+/// Provides means to manage image buffers within the .
+///
+public class AzureBlobStorageCacheResolver : IImageCacheResolver
{
+ private readonly BlobClient blob;
+
///
- /// Provides means to manage image buffers within the .
+ /// Initializes a new instance of the class.
///
- public class AzureBlobStorageCacheResolver : IImageCacheResolver
- {
- private readonly BlobClient blob;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The Azure blob.
- public AzureBlobStorageCacheResolver(BlobClient blob)
- => this.blob = blob;
+ /// The Azure blob.
+ public AzureBlobStorageCacheResolver(BlobClient blob)
+ => this.blob = blob;
- ///
- public async Task GetMetaDataAsync()
- {
- // I've had a good read through the SDK source and I believe we cannot get
- // a 304 here since 'If-Modified-Since' header is not set by default.
- BlobProperties properties = await this.blob.GetPropertiesAsync();
- return ImageCacheMetadata.FromDictionary(properties.Metadata);
- }
-
- ///
- public Task OpenReadAsync() => this.blob.OpenReadAsync();
+ ///
+ public async Task GetMetaDataAsync()
+ {
+ // I've had a good read through the SDK source and I believe we cannot get
+ // a 304 here since 'If-Modified-Since' header is not set by default.
+ BlobProperties properties = await this.blob.GetPropertiesAsync();
+ return ImageCacheMetadata.FromDictionary(properties.Metadata);
}
+
+ ///
+ public Task OpenReadAsync() => this.blob.OpenReadAsync();
}
diff --git a/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageImageResolver.cs b/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageImageResolver.cs
index 118aeaf3..dfb79c3f 100644
--- a/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageImageResolver.cs
+++ b/src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageImageResolver.cs
@@ -1,52 +1,49 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.IO;
using System.Net.Http.Headers;
-using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
-namespace SixLabors.ImageSharp.Web.Resolvers.Azure
+namespace SixLabors.ImageSharp.Web.Resolvers.Azure;
+
+///
+/// Provides means to manage image buffers within the Azure Blob file system.
+///
+public class AzureBlobStorageImageResolver : IImageResolver
{
+ private readonly BlobClient blob;
+
///
- /// Provides means to manage image buffers within the Azure Blob file system.
+ /// Initializes a new instance of the class.
///
- public class AzureBlobStorageImageResolver : IImageResolver
- {
- private readonly BlobClient blob;
+ /// The Azure blob.
+ public AzureBlobStorageImageResolver(BlobClient blob)
+ => this.blob = blob;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The Azure blob.
- public AzureBlobStorageImageResolver(BlobClient blob)
- => this.blob = blob;
+ ///
+ public async Task GetMetaDataAsync()
+ {
+ // I've had a good read through the SDK source and I believe we cannot get
+ // a 304 here since 'If-Modified-Since' header is not set by default.
+ BlobProperties properties = (await this.blob.GetPropertiesAsync()).Value;
- ///
- public async Task GetMetaDataAsync()
+ // Try to parse the max age from the source. If it's not zero then we pass it along
+ // to set the cache control headers for the response.
+ TimeSpan maxAge = TimeSpan.MinValue;
+ if (CacheControlHeaderValue.TryParse(properties.CacheControl, out CacheControlHeaderValue cacheControl))
{
- // I've had a good read through the SDK source and I believe we cannot get
- // a 304 here since 'If-Modified-Since' header is not set by default.
- BlobProperties properties = (await this.blob.GetPropertiesAsync()).Value;
-
- // Try to parse the max age from the source. If it's not zero then we pass it along
- // to set the cache control headers for the response.
- TimeSpan maxAge = TimeSpan.MinValue;
- if (CacheControlHeaderValue.TryParse(properties.CacheControl, out CacheControlHeaderValue cacheControl))
+ // Weirdly passing null to TryParse returns true.
+ if (cacheControl?.MaxAge.HasValue == true)
{
- // Weirdly passing null to TryParse returns true.
- if (cacheControl?.MaxAge.HasValue == true)
- {
- maxAge = cacheControl.MaxAge.Value;
- }
+ maxAge = cacheControl.MaxAge.Value;
}
-
- return new ImageMetadata(properties.LastModified.UtcDateTime, maxAge, properties.ContentLength);
}
- ///
- public Task OpenReadAsync() => this.blob.OpenReadAsync();
+ return new ImageMetadata(properties.LastModified.UtcDateTime, maxAge, properties.ContentLength);
}
+
+ ///
+ public Task OpenReadAsync() => this.blob.OpenReadAsync();
}
diff --git a/src/ImageSharp.Web/AsyncHelper.cs b/src/ImageSharp.Web/AsyncHelper.cs
index 342880c7..8bc52d93 100644
--- a/src/ImageSharp.Web/AsyncHelper.cs
+++ b/src/ImageSharp.Web/AsyncHelper.cs
@@ -1,58 +1,55 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
using System.Globalization;
-using System.Threading;
-using System.Threading.Tasks;
-namespace SixLabors.ImageSharp.Web
+namespace SixLabors.ImageSharp.Web;
+
+///
+///
+///
+internal static class AsyncHelper
{
+ private static readonly TaskFactory TaskFactory
+ = new(
+ CancellationToken.None,
+ TaskCreationOptions.None,
+ TaskContinuationOptions.None,
+ TaskScheduler.Default);
+
///
- ///
+ /// Executes an async method synchronously.
///
- internal static class AsyncHelper
+ /// The task to excecute.
+ public static void RunSync(Func task)
{
- private static readonly TaskFactory TaskFactory
- = new(
- CancellationToken.None,
- TaskCreationOptions.None,
- TaskContinuationOptions.None,
- TaskScheduler.Default);
-
- ///
- /// Executes an async method synchronously.
- ///
- /// The task to excecute.
- public static void RunSync(Func task)
+ CultureInfo cultureUi = CultureInfo.CurrentUICulture;
+ CultureInfo culture = CultureInfo.CurrentCulture;
+ TaskFactory.StartNew(() =>
{
- CultureInfo cultureUi = CultureInfo.CurrentUICulture;
- CultureInfo culture = CultureInfo.CurrentCulture;
- TaskFactory.StartNew(() =>
- {
- Thread.CurrentThread.CurrentCulture = culture;
- Thread.CurrentThread.CurrentUICulture = cultureUi;
- return task();
- }).Unwrap().GetAwaiter().GetResult();
- }
+ Thread.CurrentThread.CurrentCulture = culture;
+ Thread.CurrentThread.CurrentUICulture = cultureUi;
+ return task();
+ }).Unwrap().GetAwaiter().GetResult();
+ }
- ///
- /// Executes an async method which has
- /// a return type synchronously.
- ///
- /// The type of result to return.
- /// The task to excecute.
- /// The .
- public static TResult RunSync(Func> task)
+ ///
+ /// Executes an async method which has
+ /// a return type synchronously.
+ ///
+ /// The type of result to return.
+ /// The task to excecute.
+ /// The .
+ public static TResult RunSync(Func> task)
+ {
+ CultureInfo cultureUi = CultureInfo.CurrentUICulture;
+ CultureInfo culture = CultureInfo.CurrentCulture;
+ return TaskFactory.StartNew(() =>
{
- CultureInfo cultureUi = CultureInfo.CurrentUICulture;
- CultureInfo culture = CultureInfo.CurrentCulture;
- return TaskFactory.StartNew(() =>
- {
- Thread.CurrentThread.CurrentCulture = culture;
- Thread.CurrentThread.CurrentUICulture = cultureUi;
- return task();
- }).Unwrap().GetAwaiter().GetResult();
- }
+ Thread.CurrentThread.CurrentCulture = culture;
+ Thread.CurrentThread.CurrentUICulture = cultureUi;
+ return task();
+ }).Unwrap().GetAwaiter().GetResult();
}
}
diff --git a/src/ImageSharp.Web/Caching/HexEncoder.cs b/src/ImageSharp.Web/Caching/HexEncoder.cs
index 0f72a5b6..7687cc3f 100644
--- a/src/ImageSharp.Web/Caching/HexEncoder.cs
+++ b/src/ImageSharp.Web/Caching/HexEncoder.cs
@@ -1,60 +1,58 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
-using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Provides methods for encoding byte arrays into hexidecimal strings.
+///
+internal static class HexEncoder
{
+ // LUT's that provide the hexidecimal representation of each possible byte value.
+ private static readonly char[] HexLutBase = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ // The base LUT arranged in 16x each item order. 0 * 16, 1 * 16, .... F * 16
+ private static readonly char[] HexLutHi = Enumerable.Range(0, 256).Select(x => HexLutBase[x / 0x10]).ToArray();
+
+ // The base LUT repeated 16x.
+ private static readonly char[] HexLutLo = Enumerable.Range(0, 256).Select(x => HexLutBase[x % 0x10]).ToArray();
+
///
- /// Provides methods for encoding byte arrays into hexidecimal strings.
+ /// Converts a to a hexidecimal formatted padded to 2 digits.
///
- internal static class HexEncoder
+ /// The bytes.
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe string Encode(ReadOnlySpan bytes)
{
- // LUT's that provide the hexidecimal representation of each possible byte value.
- private static readonly char[] HexLutBase = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
- // The base LUT arranged in 16x each item order. 0 * 16, 1 * 16, .... F * 16
- private static readonly char[] HexLutHi = Enumerable.Range(0, 256).Select(x => HexLutBase[x / 0x10]).ToArray();
-
- // The base LUT repeated 16x.
- private static readonly char[] HexLutLo = Enumerable.Range(0, 256).Select(x => HexLutBase[x % 0x10]).ToArray();
-
- ///
- /// Converts a to a hexidecimal formatted padded to 2 digits.
- ///
- /// The bytes.
- /// The .
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe string Encode(ReadOnlySpan bytes)
+ fixed (byte* bytesPtr = bytes)
{
- fixed (byte* bytesPtr = bytes)
+ return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length), (chars, args) =>
{
- return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length), (chars, args) =>
- {
- var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length);
- EncodeToUtf16(ros, chars);
- });
- }
+ var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length);
+ EncodeToUtf16(ros, chars);
+ });
}
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void EncodeToUtf16(ReadOnlySpan bytes, Span chars)
- {
- ref byte bytesRef = ref MemoryMarshal.GetReference(bytes);
- ref char charRef = ref MemoryMarshal.GetReference(chars);
- ref char hiRef = ref MemoryMarshal.GetReference(HexLutHi);
- ref char lowRef = ref MemoryMarshal.GetReference(HexLutLo);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void EncodeToUtf16(ReadOnlySpan bytes, Span chars)
+ {
+ ref byte bytesRef = ref MemoryMarshal.GetReference(bytes);
+ ref char charRef = ref MemoryMarshal.GetReference(chars);
+ ref char hiRef = ref MemoryMarshal.GetReference(HexLutHi);
+ ref char lowRef = ref MemoryMarshal.GetReference(HexLutLo);
- int index = 0;
- for (int i = 0; i < bytes.Length; i++)
- {
- byte byteIndex = Unsafe.Add(ref bytesRef, i);
- Unsafe.Add(ref charRef, index++) = Unsafe.Add(ref hiRef, byteIndex);
- Unsafe.Add(ref charRef, index++) = Unsafe.Add(ref lowRef, byteIndex);
- }
+ int index = 0;
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ byte byteIndex = Unsafe.Add(ref bytesRef, i);
+ Unsafe.Add(ref charRef, index++) = Unsafe.Add(ref hiRef, byteIndex);
+ Unsafe.Add(ref charRef, index++) = Unsafe.Add(ref lowRef, byteIndex);
}
}
}
diff --git a/src/ImageSharp.Web/Caching/ICacheHash.cs b/src/ImageSharp.Web/Caching/ICacheHash.cs
index 3d42246e..bf262d0e 100644
--- a/src/ImageSharp.Web/Caching/ICacheHash.cs
+++ b/src/ImageSharp.Web/Caching/ICacheHash.cs
@@ -1,19 +1,19 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Defines a contract that allows the creation of hashed file names for storing cached images.
+///
+public interface ICacheHash
{
///
- /// Defines a contract that allows the creation of hashed file names for storing cached images.
+ /// Returns the hashed file name for the cached image file.
///
- public interface ICacheHash
- {
- ///
- /// Returns the hashed file name for the cached image file.
- ///
- /// The input value to hash.
- /// The length of the returned hash without any extensions.
- /// The .
- string Create(string value, uint length);
- }
+ /// The input value to hash.
+ /// The length of the returned hash without any extensions.
+ /// The .
+ string Create(string value, uint length);
}
diff --git a/src/ImageSharp.Web/Caching/ICacheKey.cs b/src/ImageSharp.Web/Caching/ICacheKey.cs
index 227e2b72..2a23d42a 100644
--- a/src/ImageSharp.Web/Caching/ICacheKey.cs
+++ b/src/ImageSharp.Web/Caching/ICacheKey.cs
@@ -1,24 +1,24 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
using Microsoft.AspNetCore.Http;
using SixLabors.ImageSharp.Web.Commands;
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Defines a contract that allows the creation of cache keys (used by to create hashed file names for storing cached images).
+///
+public interface ICacheKey
{
///
- /// Defines a contract that allows the creation of cache keys (used by to create hashed file names for storing cached images).
+ /// Creates the cache key based on the specified context and commands.
///
- public interface ICacheKey
- {
- ///
- /// Creates the cache key based on the specified context and commands.
- ///
- /// The HTTP context.
- /// The commands.
- ///
- /// The cache key.
- ///
- string Create(HttpContext context, CommandCollection commands);
- }
+ /// The HTTP context.
+ /// The commands.
+ ///
+ /// The cache key.
+ ///
+ string Create(HttpContext context, CommandCollection commands);
}
diff --git a/src/ImageSharp.Web/Caching/IImageCache.cs b/src/ImageSharp.Web/Caching/IImageCache.cs
index 20bd6e64..bdd22c6b 100644
--- a/src/ImageSharp.Web/Caching/IImageCache.cs
+++ b/src/ImageSharp.Web/Caching/IImageCache.cs
@@ -1,32 +1,30 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System.IO;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Web.Resolvers;
// TODO: Do we add cleanup to this? Scalable caches probably shouldn't do so.
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Specifies the contract for caching images.
+///
+public interface IImageCache
{
///
- /// Specifies the contract for caching images.
+ /// Gets the image resolver associated with the specified key.
///
- public interface IImageCache
- {
- ///
- /// Gets the image resolver associated with the specified key.
- ///
- /// The cache key.
- /// The .
- Task GetAsync(string key);
+ /// The cache key.
+ /// The .
+ Task GetAsync(string key);
- ///
- /// Sets the value associated with the specified key.
- ///
- /// The cache key.
- /// The stream containing the image to store.
- /// The associated with the image to store.
- /// The task.
- Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata);
- }
+ ///
+ /// Sets the value associated with the specified key.
+ ///
+ /// The cache key.
+ /// The stream containing the image to store.
+ /// The associated with the image to store.
+ /// The task.
+ Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata);
}
diff --git a/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs b/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs
index 0d72b045..e3abe213 100644
--- a/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs
+++ b/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs
@@ -1,38 +1,39 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
+using System.Globalization;
using System.Text;
using Microsoft.AspNetCore.Http;
using SixLabors.ImageSharp.Web.Commands;
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Maintained for compatibility purposes only this cache key implementation generates the same
+/// out as the V1 middleware. If possible, it is recommended to use the .
+///
+public class LegacyV1CacheKey : ICacheKey
{
- ///
- /// Maintained for compatibility purposes only this cache key implementation generates the same
- /// out as the V1 middleware. If possible, it is recommended to use the .
- ///
- public class LegacyV1CacheKey : ICacheKey
+ ///
+ public string Create(HttpContext context, CommandCollection commands)
{
- ///
- public string Create(HttpContext context, CommandCollection commands)
- {
- var sb = new StringBuilder(context.Request.Host.ToString());
+ StringBuilder sb = new(context.Request.Host.ToString());
- string pathBase = context.Request.PathBase.ToString();
- if (!string.IsNullOrWhiteSpace(pathBase))
- {
- sb.AppendFormat("{0}/", pathBase);
- }
+ string pathBase = context.Request.PathBase.ToString();
+ if (!string.IsNullOrWhiteSpace(pathBase))
+ {
+ sb.AppendFormat(CultureInfo.InvariantCulture, "{0}/", pathBase);
+ }
- string path = context.Request.Path.ToString();
- if (!string.IsNullOrWhiteSpace(path))
- {
- sb.Append(path);
- }
+ string path = context.Request.Path.ToString();
+ if (!string.IsNullOrWhiteSpace(path))
+ {
+ sb.Append(path);
+ }
- sb.Append(QueryString.Create(commands));
+ sb.Append(QueryString.Create(commands));
- return sb.ToString().ToLowerInvariant();
- }
+ return sb.ToString().ToLowerInvariant();
}
}
diff --git a/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs b/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs
index ce16a203..8e9e7494 100644
--- a/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs
+++ b/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs
@@ -1,376 +1,371 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Pseudo LRU implementation where LRU list is composed of 3 segments: hot, warm and cold. Cost of maintaining
+/// segments is amortized across requests. Items are only cycled when capacity is exceeded. Pure read does
+/// not cycle items if all segments are within capacity constraints.
+/// There are no global locks. On cache miss, a new item is added. Tail items in each segment are dequeued,
+/// examined, and are either enqueued or discarded.
+/// This scheme of hot, warm and cold is based on the implementation used in MemCached described online here:
+/// https://memcached.org/blog/modern-lru/
+///
+///
+/// Each segment has a capacity. When segment capacity is exceeded, items are moved as follows:
+/// 1. New items are added to hot, WasAccessed = false
+/// 2. When items are accessed, update WasAccessed = true
+/// 3. When items are moved WasAccessed is set to false.
+/// 4. When hot is full, hot tail is moved to either Warm or Cold depending on WasAccessed.
+/// 5. When warm is full, warm tail is moved to warm head or cold depending on WasAccessed.
+/// 6. When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed.
+///
+internal class ConcurrentTLruCache
{
+ private readonly ConcurrentDictionary> dictionary;
+
+ private readonly ConcurrentQueue> hotQueue;
+ private readonly ConcurrentQueue> warmQueue;
+ private readonly ConcurrentQueue> coldQueue;
+
+ // Maintain count outside ConcurrentQueue, since ConcurrentQueue.Count holds a global lock
+ private int hotCount;
+ private int warmCount;
+ private int coldCount;
+
+ private readonly int hotCapacity;
+ private readonly int warmCapacity;
+ private readonly int coldCapacity;
+
+ private readonly TLruLongTicksPolicy policy;
+
///
- /// Pseudo LRU implementation where LRU list is composed of 3 segments: hot, warm and cold. Cost of maintaining
- /// segments is amortized across requests. Items are only cycled when capacity is exceeded. Pure read does
- /// not cycle items if all segments are within capacity constraints.
- /// There are no global locks. On cache miss, a new item is added. Tail items in each segment are dequeued,
- /// examined, and are either enqueued or discarded.
- /// This scheme of hot, warm and cold is based on the implementation used in MemCached described online here:
- /// https://memcached.org/blog/modern-lru/
+ /// Initializes a new instance of the class with the specified capacity and time to live that has the default.
+ /// concurrency level, and uses the default comparer for the key type.
///
- ///
- /// Each segment has a capacity. When segment capacity is exceeded, items are moved as follows:
- /// 1. New items are added to hot, WasAccessed = false
- /// 2. When items are accessed, update WasAccessed = true
- /// 3. When items are moved WasAccessed is set to false.
- /// 4. When hot is full, hot tail is moved to either Warm or Cold depending on WasAccessed.
- /// 5. When warm is full, warm tail is moved to warm head or cold depending on WasAccessed.
- /// 6. When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed.
- ///
- internal class ConcurrentTLruCache
+ /// The maximum number of elements that the FastConcurrentTLru can contain.
+ /// The time to live for cached values.
+ public ConcurrentTLruCache(int capacity, TimeSpan timeToLive)
+ : this(Environment.ProcessorCount, capacity, EqualityComparer.Default, timeToLive)
{
- private readonly ConcurrentDictionary> dictionary;
-
- private readonly ConcurrentQueue> hotQueue;
- private readonly ConcurrentQueue> warmQueue;
- private readonly ConcurrentQueue> coldQueue;
-
- // Maintain count outside ConcurrentQueue, since ConcurrentQueue.Count holds a global lock
- private int hotCount;
- private int warmCount;
- private int coldCount;
-
- private readonly int hotCapacity;
- private readonly int warmCapacity;
- private readonly int coldCapacity;
-
- private readonly TLruLongTicksPolicy policy;
-
- ///
- /// Initializes a new instance of the class with the specified capacity and time to live that has the default.
- /// concurrency level, and uses the default comparer for the key type.
- ///
- /// The maximum number of elements that the FastConcurrentTLru can contain.
- /// The time to live for cached values.
- public ConcurrentTLruCache(int capacity, TimeSpan timeToLive)
- : this(Environment.ProcessorCount, capacity, EqualityComparer.Default, timeToLive)
- {
- }
+ }
- ///
- /// Initializes a new instance of the class that has the specified concurrency level, has the.
- /// specified initial capacity, uses the specified , and has the specified time to live.
- ///
- /// The estimated number of threads that will update the ConcurrentTLru concurrently.
- /// The maximum number of elements that the ConcurrentTLru can contain.
- /// The implementation to use when comparing keys.
- /// The time to live for cached values.
- public ConcurrentTLruCache(int concurrencyLevel, int capacity, IEqualityComparer comparer, TimeSpan timeToLive)
- {
- Guard.MustBeGreaterThanOrEqualTo(capacity, 3, nameof(capacity));
- Guard.NotNull(comparer, nameof(comparer));
+ ///
+ /// Initializes a new instance of the class that has the specified concurrency level, has the.
+ /// specified initial capacity, uses the specified , and has the specified time to live.
+ ///
+ /// The estimated number of threads that will update the ConcurrentTLru concurrently.
+ /// The maximum number of elements that the ConcurrentTLru can contain.
+ /// The implementation to use when comparing keys.
+ /// The time to live for cached values.
+ public ConcurrentTLruCache(int concurrencyLevel, int capacity, IEqualityComparer comparer, TimeSpan timeToLive)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(capacity, 3, nameof(capacity));
+ Guard.NotNull(comparer, nameof(comparer));
- this.hotCapacity = capacity / 3;
- this.warmCapacity = capacity / 3;
- this.coldCapacity = capacity / 3;
+ this.hotCapacity = capacity / 3;
+ this.warmCapacity = capacity / 3;
+ this.coldCapacity = capacity / 3;
- this.hotQueue = new ConcurrentQueue>();
- this.warmQueue = new ConcurrentQueue>();
- this.coldQueue = new ConcurrentQueue>();
+ this.hotQueue = new ConcurrentQueue>();
+ this.warmQueue = new ConcurrentQueue>();
+ this.coldQueue = new ConcurrentQueue>();
- int dictionaryCapacity = this.hotCapacity + this.warmCapacity + this.coldCapacity + 1;
+ int dictionaryCapacity = this.hotCapacity + this.warmCapacity + this.coldCapacity + 1;
- this.dictionary = new ConcurrentDictionary>(concurrencyLevel, dictionaryCapacity, comparer);
- this.policy = new TLruLongTicksPolicy(timeToLive);
- }
+ this.dictionary = new ConcurrentDictionary>(concurrencyLevel, dictionaryCapacity, comparer);
+ this.policy = new TLruLongTicksPolicy(timeToLive);
+ }
- // No lock count: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/
- public int Count => this.dictionary.Skip(0).Count();
+ // No lock count: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/
+ public int Count => this.dictionary.Skip(0).Count();
- public int HotCount => this.hotCount;
+ public int HotCount => this.hotCount;
- public int WarmCount => this.warmCount;
+ public int WarmCount => this.warmCount;
- public int ColdCount => this.coldCount;
+ public int ColdCount => this.coldCount;
- ///
- /// Attempts to get the value associated with the specified key from the cache.
- ///
- /// The key of the value to get.
- /// When this method returns, contains the object from the cache that has the specified key, or the default value of the type if the operation failed.
- /// if the key was found in the cache; otherwise, .
- public bool TryGet(TKey key, out TValue value)
+ ///
+ /// Attempts to get the value associated with the specified key from the cache.
+ ///
+ /// The key of the value to get.
+ /// When this method returns, contains the object from the cache that has the specified key, or the default value of the type if the operation failed.
+ /// if the key was found in the cache; otherwise, .
+ public bool TryGet(TKey key, out TValue value)
+ {
+ if (this.dictionary.TryGetValue(key, out LongTickCountLruItem item))
{
- if (this.dictionary.TryGetValue(key, out LongTickCountLruItem item))
- {
- return this.GetOrDiscard(item, out value);
- }
+ return this.GetOrDiscard(item, out value);
+ }
+ value = default;
+ return false;
+ }
+
+ // AggressiveInlining forces the JIT to inline policy.ShouldDiscard(). For LRU policy
+ // the first branch is completely eliminated due to JIT time constant propogation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool GetOrDiscard(LongTickCountLruItem item, out TValue value)
+ {
+ if (this.policy.ShouldDiscard(item))
+ {
+ this.Move(item, ItemDestination.Remove);
value = default;
return false;
}
- // AggressiveInlining forces the JIT to inline policy.ShouldDiscard(). For LRU policy
- // the first branch is completely eliminated due to JIT time constant propogation.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool GetOrDiscard(LongTickCountLruItem item, out TValue value)
- {
- if (this.policy.ShouldDiscard(item))
- {
- this.Move(item, ItemDestination.Remove);
- value = default;
- return false;
- }
+ value = item.Value;
+ TLruLongTicksPolicy.Touch(item);
+ return true;
+ }
- value = item.Value;
- this.policy.Touch(item);
- return true;
+ ///
+ /// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the
+ /// existing value if the key already exists.
+ ///
+ /// The key of the element to add.
+ /// The factory function used to generate a value for the key.
+ /// The value for the key. This will be either the existing value for the key if the key is already
+ /// in the cache, or the new value if the key was not in the dictionary.
+ public TValue GetOrAdd(TKey key, Func valueFactory)
+ {
+ if (this.TryGet(key, out TValue value))
+ {
+ return value;
}
- ///
- /// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the
- /// existing value if the key already exists.
- ///
- /// The key of the element to add.
- /// The factory function used to generate a value for the key.
- /// The value for the key. This will be either the existing value for the key if the key is already
- /// in the cache, or the new value if the key was not in the dictionary.
- public TValue GetOrAdd(TKey key, Func valueFactory)
- {
- if (this.TryGet(key, out TValue value))
- {
- return value;
- }
+ // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
+ // This is identical logic in ConcurrentDictionary.GetOrAdd method.
+ LongTickCountLruItem newItem = TLruLongTicksPolicy.CreateItem(key, valueFactory(key));
- // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
- // This is identical logic in ConcurrentDictionary.GetOrAdd method.
- LongTickCountLruItem newItem = this.policy.CreateItem(key, valueFactory(key));
+ if (this.dictionary.TryAdd(key, newItem))
+ {
+ this.hotQueue.Enqueue(newItem);
+ Interlocked.Increment(ref this.hotCount);
+ this.Cycle();
+ return newItem.Value;
+ }
- if (this.dictionary.TryAdd(key, newItem))
- {
- this.hotQueue.Enqueue(newItem);
- Interlocked.Increment(ref this.hotCount);
- this.Cycle();
- return newItem.Value;
- }
+ return this.GetOrAdd(key, valueFactory);
+ }
- return this.GetOrAdd(key, valueFactory);
+ ///
+ /// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the
+ /// existing value if the key already exists.
+ ///
+ /// The key of the element to add.
+ /// The factory function used to asynchronously generate a value for the key.
+ /// A task that represents the asynchronous operation.
+ public async Task GetOrAddAsync(TKey key, Func> valueFactory)
+ {
+ if (this.TryGet(key, out TValue value))
+ {
+ return value;
}
- ///
- /// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the
- /// existing value if the key already exists.
- ///
- /// The key of the element to add.
- /// The factory function used to asynchronously generate a value for the key.
- /// A task that represents the asynchronous operation.
- public async Task GetOrAddAsync(TKey key, Func> valueFactory)
+ // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
+ // This is identical logic in ConcurrentDictionary.GetOrAdd method.
+ LongTickCountLruItem newItem = TLruLongTicksPolicy.CreateItem(key, await valueFactory(key).ConfigureAwait(false));
+
+ if (this.dictionary.TryAdd(key, newItem))
{
- if (this.TryGet(key, out TValue value))
- {
- return value;
- }
+ this.hotQueue.Enqueue(newItem);
+ Interlocked.Increment(ref this.hotCount);
+ this.Cycle();
+ return newItem.Value;
+ }
- // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
- // This is identical logic in ConcurrentDictionary.GetOrAdd method.
- LongTickCountLruItem newItem = this.policy.CreateItem(key, await valueFactory(key).ConfigureAwait(false));
+ return await this.GetOrAddAsync(key, valueFactory).ConfigureAwait(false);
+ }
- if (this.dictionary.TryAdd(key, newItem))
+ ///
+ /// Attempts to remove and return the value that has the specified key from the cache.
+ ///
+ /// The key of the element to remove.
+ /// if the object was removed successfully; otherwise, .
+ public bool TryRemove(TKey key)
+ {
+ // Possible race condition:
+ // Thread A TryRemove(1), removes LruItem1, has reference to removed item but not yet marked as removed
+ // Thread B GetOrAdd(1) => Adds LruItem1*
+ // Thread C GetOrAdd(2), Cycle, Move(LruItem1, Removed)
+ //
+ // Thread C can run and remove LruItem1* from this.dictionary before Thread A has marked LruItem1 as removed.
+ //
+ // In this situation, a subsequent attempt to fetch 1 will be a miss. The queues will still contain LruItem1*,
+ // and it will not be marked as removed. If key 1 is fetched while LruItem1* is still in the queue, there will
+ // be two queue entries for key 1, and neither is marked as removed. Thus when LruItem1 * ages out, it will
+ // incorrectly remove 1 from the dictionary, and this cycle can repeat.
+ if (this.dictionary.TryGetValue(key, out LongTickCountLruItem existing))
+ {
+ if (existing.WasRemoved)
{
- this.hotQueue.Enqueue(newItem);
- Interlocked.Increment(ref this.hotCount);
- this.Cycle();
- return newItem.Value;
+ return false;
}
- return await this.GetOrAddAsync(key, valueFactory).ConfigureAwait(false);
- }
-
- ///
- /// Attempts to remove and return the value that has the specified key from the cache.
- ///
- /// The key of the element to remove.
- /// if the object was removed successfully; otherwise, .
- public bool TryRemove(TKey key)
- {
- // Possible race condition:
- // Thread A TryRemove(1), removes LruItem1, has reference to removed item but not yet marked as removed
- // Thread B GetOrAdd(1) => Adds LruItem1*
- // Thread C GetOrAdd(2), Cycle, Move(LruItem1, Removed)
- //
- // Thread C can run and remove LruItem1* from this.dictionary before Thread A has marked LruItem1 as removed.
- //
- // In this situation, a subsequent attempt to fetch 1 will be a miss. The queues will still contain LruItem1*,
- // and it will not be marked as removed. If key 1 is fetched while LruItem1* is still in the queue, there will
- // be two queue entries for key 1, and neither is marked as removed. Thus when LruItem1 * ages out, it will
- // incorrectly remove 1 from the dictionary, and this cycle can repeat.
- if (this.dictionary.TryGetValue(key, out LongTickCountLruItem existing))
+ lock (existing)
{
if (existing.WasRemoved)
{
return false;
}
- lock (existing)
- {
- if (existing.WasRemoved)
- {
- return false;
- }
+ existing.WasRemoved = true;
+ }
- existing.WasRemoved = true;
- }
+ if (this.dictionary.TryRemove(key, out LongTickCountLruItem removedItem))
+ {
+ // Mark as not accessed, it will later be cycled out of the queues because it can never be fetched
+ // from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled
+ // from the queue.
+ removedItem.WasAccessed = false;
- if (this.dictionary.TryRemove(key, out LongTickCountLruItem removedItem))
+ if (removedItem.Value is IDisposable d)
{
- // Mark as not accessed, it will later be cycled out of the queues because it can never be fetched
- // from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled
- // from the queue.
- removedItem.WasAccessed = false;
-
- if (removedItem.Value is IDisposable d)
- {
- d.Dispose();
- }
-
- return true;
+ d.Dispose();
}
- }
- return false;
+ return true;
+ }
}
- private void Cycle()
- {
- // There will be races when queue count == queue capacity. Two threads may each dequeue items.
- // This will prematurely free slots for the next caller. Each thread will still only cycle at most 5 items.
- // Since TryDequeue is thread safe, only 1 thread can dequeue each item. Thus counts and queue state will always
- // converge on correct over time.
- this.CycleHot();
-
- // Multi-threaded stress tests show that due to races, the warm and cold count can increase beyond capacity when
- // hit rate is very high. Double cycle results in stable count under all conditions. When contention is low,
- // secondary cycles have no effect.
- this.CycleWarm();
- this.CycleWarm();
- this.CycleCold();
- this.CycleCold();
- }
+ return false;
+ }
+
+ private void Cycle()
+ {
+ // There will be races when queue count == queue capacity. Two threads may each dequeue items.
+ // This will prematurely free slots for the next caller. Each thread will still only cycle at most 5 items.
+ // Since TryDequeue is thread safe, only 1 thread can dequeue each item. Thus counts and queue state will always
+ // converge on correct over time.
+ this.CycleHot();
+
+ // Multi-threaded stress tests show that due to races, the warm and cold count can increase beyond capacity when
+ // hit rate is very high. Double cycle results in stable count under all conditions. When contention is low,
+ // secondary cycles have no effect.
+ this.CycleWarm();
+ this.CycleWarm();
+ this.CycleCold();
+ this.CycleCold();
+ }
- private void CycleHot()
+ private void CycleHot()
+ {
+ if (this.hotCount > this.hotCapacity)
{
- if (this.hotCount > this.hotCapacity)
- {
- Interlocked.Decrement(ref this.hotCount);
+ Interlocked.Decrement(ref this.hotCount);
- if (this.hotQueue.TryDequeue(out LongTickCountLruItem item))
- {
- ItemDestination where = this.policy.RouteHot(item);
- this.Move(item, where);
- }
- else
- {
- Interlocked.Increment(ref this.hotCount);
- }
+ if (this.hotQueue.TryDequeue(out LongTickCountLruItem item))
+ {
+ ItemDestination where = this.policy.RouteHot(item);
+ this.Move(item, where);
+ }
+ else
+ {
+ Interlocked.Increment(ref this.hotCount);
}
}
+ }
- private void CycleWarm()
+ private void CycleWarm()
+ {
+ if (this.warmCount > this.warmCapacity)
{
- if (this.warmCount > this.warmCapacity)
+ Interlocked.Decrement(ref this.warmCount);
+
+ if (this.warmQueue.TryDequeue(out LongTickCountLruItem item))
{
- Interlocked.Decrement(ref this.warmCount);
+ ItemDestination where = this.policy.RouteWarm(item);
- if (this.warmQueue.TryDequeue(out LongTickCountLruItem item))
+ // When the warm queue is full, we allow an overflow of 1 item before redirecting warm items to cold.
+ // This only happens when hit rate is high, in which case we can consider all items relatively equal in
+ // terms of which was least recently used.
+ if (where == ItemDestination.Warm && this.warmCount <= this.warmCapacity)
{
- ItemDestination where = this.policy.RouteWarm(item);
-
- // When the warm queue is full, we allow an overflow of 1 item before redirecting warm items to cold.
- // This only happens when hit rate is high, in which case we can consider all items relatively equal in
- // terms of which was least recently used.
- if (where == ItemDestination.Warm && this.warmCount <= this.warmCapacity)
- {
- this.Move(item, where);
- }
- else
- {
- this.Move(item, ItemDestination.Cold);
- }
+ this.Move(item, where);
}
else
{
- Interlocked.Increment(ref this.warmCount);
+ this.Move(item, ItemDestination.Cold);
}
}
+ else
+ {
+ Interlocked.Increment(ref this.warmCount);
+ }
}
+ }
- private void CycleCold()
+ private void CycleCold()
+ {
+ if (this.coldCount > this.coldCapacity)
{
- if (this.coldCount > this.coldCapacity)
+ Interlocked.Decrement(ref this.coldCount);
+
+ if (this.coldQueue.TryDequeue(out LongTickCountLruItem item))
{
- Interlocked.Decrement(ref this.coldCount);
+ ItemDestination where = this.policy.RouteCold(item);
- if (this.coldQueue.TryDequeue(out LongTickCountLruItem item))
+ if (where == ItemDestination.Warm && this.warmCount <= this.warmCapacity)
{
- ItemDestination where = this.policy.RouteCold(item);
-
- if (where == ItemDestination.Warm && this.warmCount <= this.warmCapacity)
- {
- this.Move(item, where);
- }
- else
- {
- this.Move(item, ItemDestination.Remove);
- }
+ this.Move(item, where);
}
else
{
- Interlocked.Increment(ref this.coldCount);
+ this.Move(item, ItemDestination.Remove);
}
}
+ else
+ {
+ Interlocked.Increment(ref this.coldCount);
+ }
}
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Move(LongTickCountLruItem item, ItemDestination where)
- {
- item.WasAccessed = false;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Move(LongTickCountLruItem item, ItemDestination where)
+ {
+ item.WasAccessed = false;
- switch (where)
- {
- case ItemDestination.Warm:
- this.warmQueue.Enqueue(item);
- Interlocked.Increment(ref this.warmCount);
- break;
- case ItemDestination.Cold:
- this.coldQueue.Enqueue(item);
- Interlocked.Increment(ref this.coldCount);
- break;
- case ItemDestination.Remove:
- if (!item.WasRemoved)
+ switch (where)
+ {
+ case ItemDestination.Warm:
+ this.warmQueue.Enqueue(item);
+ Interlocked.Increment(ref this.warmCount);
+ break;
+ case ItemDestination.Cold:
+ this.coldQueue.Enqueue(item);
+ Interlocked.Increment(ref this.coldCount);
+ break;
+ case ItemDestination.Remove:
+ if (!item.WasRemoved)
+ {
+ // Avoid race where 2 threads could remove the same key - see TryRemove for details.
+ lock (item)
{
- // Avoid race where 2 threads could remove the same key - see TryRemove for details.
- lock (item)
+ if (item.WasRemoved)
{
- if (item.WasRemoved)
- {
- break;
- }
+ break;
+ }
- if (this.dictionary.TryRemove(item.Key, out LongTickCountLruItem removedItem))
+ if (this.dictionary.TryRemove(item.Key, out LongTickCountLruItem removedItem))
+ {
+ item.WasRemoved = true;
+ if (removedItem.Value is IDisposable d)
{
- item.WasRemoved = true;
- if (removedItem.Value is IDisposable d)
- {
- d.Dispose();
- }
+ d.Dispose();
}
}
}
+ }
- break;
- }
+ break;
}
}
}
diff --git a/src/ImageSharp.Web/Caching/LruCache/ItemDestination.cs b/src/ImageSharp.Web/Caching/LruCache/ItemDestination.cs
index d887e2e6..2275ccc8 100644
--- a/src/ImageSharp.Web/Caching/LruCache/ItemDestination.cs
+++ b/src/ImageSharp.Web/Caching/LruCache/ItemDestination.cs
@@ -1,12 +1,11 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+internal enum ItemDestination
{
- internal enum ItemDestination
- {
- Warm,
- Cold,
- Remove
- }
+ Warm,
+ Cold,
+ Remove
}
diff --git a/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs b/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs
index d0d24a15..7bd0a76d 100644
--- a/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs
+++ b/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs
@@ -1,38 +1,38 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+internal class LongTickCountLruItem
{
- internal class LongTickCountLruItem
- {
- private volatile bool wasAccessed;
- private volatile bool wasRemoved;
+ private volatile bool wasAccessed;
+ private volatile bool wasRemoved;
#pragma warning disable SA1401 // Fields should be private
- public readonly TKey Key;
+ public readonly TKey Key;
- public readonly TValue Value;
+ public readonly TValue Value;
#pragma warning restore SA1401 // Fields should be private
- public LongTickCountLruItem(TKey k, TValue v, long tickCount)
- {
- this.Key = k;
- this.Value = v;
- this.TickCount = tickCount;
- }
-
- public long TickCount { get; set; }
-
- public bool WasAccessed
- {
- get => this.wasAccessed;
- set => this.wasAccessed = value;
- }
-
- public bool WasRemoved
- {
- get => this.wasRemoved;
- set => this.wasRemoved = value;
- }
+ public LongTickCountLruItem(TKey k, TValue v, long tickCount)
+ {
+ this.Key = k;
+ this.Value = v;
+ this.TickCount = tickCount;
+ }
+
+ public long TickCount { get; set; }
+
+ public bool WasAccessed
+ {
+ get => this.wasAccessed;
+ set => this.wasAccessed = value;
+ }
+
+ public bool WasRemoved
+ {
+ get => this.wasRemoved;
+ set => this.wasRemoved = value;
}
}
diff --git a/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs b/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs
index e63d6233..30f28b4f 100644
--- a/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs
+++ b/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs
@@ -1,97 +1,84 @@
// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Licensed under the Six Labors Split License.
+#nullable disable
-using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
-namespace SixLabors.ImageSharp.Web.Caching
+namespace SixLabors.ImageSharp.Web.Caching;
+
+///
+/// Time aware Least Recently Used (TLRU) is a variant of LRU which discards the least
+/// recently used items first, and any item that has expired.
+///
+/// The type of key object.
+/// The type of value object.
+///
+/// This class measures time using stopwatch.
+///
+internal readonly struct TLruLongTicksPolicy
{
- ///
- /// Time aware Least Recently Used (TLRU) is a variant of LRU which discards the least
- /// recently used items first, and any item that has expired.
- ///
- ///
- /// This class measures time using stopwatch.
- ///
- internal readonly struct TLruLongTicksPolicy
- {
- private readonly long timeToLive;
+ private readonly long timeToLive;
- public TLruLongTicksPolicy(TimeSpan timeToLive)
- {
- this.timeToLive = timeToLive.Ticks;
- }
+ public TLruLongTicksPolicy(TimeSpan timeToLive)
+ => this.timeToLive = timeToLive.Ticks;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public LongTickCountLruItem CreateItem(TKey key, TValue value)
- {
- return new LongTickCountLruItem(key, value, Stopwatch.GetTimestamp());
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static LongTickCountLruItem CreateItem(TKey key, TValue value)
+ => new(key, value, Stopwatch.GetTimestamp());
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Touch(LongTickCountLruItem item)
- {
- item.WasAccessed = true;
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Touch(LongTickCountLruItem item) => item.WasAccessed = true;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool ShouldDiscard(LongTickCountLruItem item)
- {
- if (Stopwatch.GetTimestamp() - item.TickCount > this.timeToLive)
- {
- return true;
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool ShouldDiscard(LongTickCountLruItem item)
+ => Stopwatch.GetTimestamp() - item.TickCount > this.timeToLive;
- return false;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemDestination RouteHot(LongTickCountLruItem item)
+ {
+ if (this.ShouldDiscard(item))
+ {
+ return ItemDestination.Remove;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemDestination RouteHot(LongTickCountLruItem item)
+ if (item.WasAccessed)
{
- if (this.ShouldDiscard(item))
- {
- return ItemDestination.Remove;
- }
+ return ItemDestination.Warm;
+ }
- if (item.WasAccessed)
- {
- return ItemDestination.Warm;
- }
+ return ItemDestination.Cold;
+ }
- return ItemDestination.Cold;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemDestination RouteWarm(LongTickCountLruItem item)
+ {
+ if (this.ShouldDiscard(item))
+ {
+ return ItemDestination.Remove;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemDestination RouteWarm(LongTickCountLruItem item)
+ if (item.WasAccessed)
{
- if (this.ShouldDiscard(item))
- {
- return ItemDestination.Remove;
- }
+ return ItemDestination.Warm;
+ }
- if (item.WasAccessed)
- {
- return ItemDestination.Warm;
- }
+ return ItemDestination.Cold;
+ }
- return ItemDestination.Cold;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemDestination RouteCold(LongTickCountLruItem