diff --git a/.gitattributes b/.gitattributes index 2fdea90e..ff4ec940 100644 --- a/.gitattributes +++ b/.gitattributes @@ -64,18 +64,19 @@ # Set explicit file behavior to: # treat as text # normalize to Unix-style line endings and -# use a union merge when resoling conflicts +# use a union merge when resolving conflicts ############################################################################### *.csproj text eol=lf merge=union *.dbproj text eol=lf merge=union *.fsproj text eol=lf merge=union *.ncrunchproject text eol=lf merge=union *.vbproj text eol=lf merge=union +*.shproj text eol=lf merge=union ############################################################################### # Set explicit file behavior to: # treat as text # normalize to Windows-style line endings and -# use a union merge when resoling conflicts +# use a union merge when resolving conflicts ############################################################################### *.sln text eol=crlf merge=union ############################################################################### diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 97aafb97..78220623 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -189,9 +189,15 @@ jobs: shell: pwsh run: ./ci-pack.ps1 - - name: MyGet Publish + - name: Feedz Publish shell: pwsh run: | - dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package - dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json - # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org + dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json --skip-duplicate + dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/symbols --skip-duplicate + + - name: NuGet Publish + if: ${{ startsWith(github.ref, 'refs/tags/') }} + shell: pwsh + run: | + dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/README.md b/README.md index 0ded0990..189b9788 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,11 @@ For more information, see the [.NET Foundation Code of Conduct](https://dotnetfo ### Installation -Install stable releases via Nuget; development releases are available via MyGet. +Install stable releases via Nuget; development releases are available via Feedz.io. -| Package Name | Release (NuGet) | Nightly (MyGet) | +| Package Name | Release (NuGet) | Nightly (Feedz.io) | |--------------------------------|-----------------|-----------------| -| `SixLabors.ImageSharp.Web` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Web.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Web/) | [![MyGet](https://img.shields.io/myget/sixlabors/vpre/SixLabors.ImageSharp.Web.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Web) | +| `SixLabors.ImageSharp.Web` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Web.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Web/) | [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https%3A%2F%2Ff.feedz.io%2Fsixlabors%2Fsixlabors%2Fshield%2FSixLabors.ImageSharp.Web%2Flatest)](https://f.feedz.io/sixlabors/sixlabors/nuget/index.json) | ## Manual build diff --git a/ci-pack.ps1 b/ci-pack.ps1 index 09f45347..55c69fb5 100644 --- a/ci-pack.ps1 +++ b/ci-pack.ps1 @@ -3,4 +3,4 @@ dotnet clean -c Release $repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" # Building for packing and publishing. -dotnet pack -c Release --output "$PSScriptRoot/artifacts" /p:RepositoryUrl=$repositoryUrl +dotnet pack -c Release -p:PackageOutputPath="$PSScriptRoot/artifacts" -p:RepositoryUrl=$repositoryUrl diff --git a/samples/ImageSharp.Web.Sample/Startup.cs b/samples/ImageSharp.Web.Sample/Startup.cs index eaf01e31..a01e8c07 100644 --- a/samples/ImageSharp.Web.Sample/Startup.cs +++ b/samples/ImageSharp.Web.Sample/Startup.cs @@ -30,50 +30,44 @@ public class Startup /// /// 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(); + => 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) - { - services.AddImageSharp(options => + => services.AddImageSharp(options => { options.Configuration = Configuration.Default; options.BrowserMaxAge = TimeSpan.FromDays(7); @@ -84,18 +78,14 @@ private void ConfigureDefaultServicesAndCustomOptions(IServiceCollection service options.OnProcessedAsync = _ => Task.CompletedTask; options.OnPrepareResponseAsync = _ => Task.CompletedTask; }); - } private void ConfigureCustomServicesAndDefaultOptions(IServiceCollection services) - { - services.AddImageSharp() - .RemoveProcessor() - .RemoveProcessor(); - } + => services.AddImageSharp() + .RemoveProcessor() + .RemoveProcessor(); private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services) - { - services.AddImageSharp(options => + => services.AddImageSharp(options => { options.Configuration = Configuration.Default; options.BrowserMaxAge = TimeSpan.FromDays(7); @@ -107,10 +97,7 @@ private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services options.OnPrepareResponseAsync = _ => Task.CompletedTask; }) .SetRequestParser() - .Configure(options => - { - options.CacheFolder = "different-cache"; - }) + .Configure(options => options.CacheFolder = "different-cache") .SetCache() .SetCacheKey() .SetCacheHash() @@ -121,7 +108,6 @@ private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services .AddProcessor() .AddProcessor() .AddProcessor(); - } /// /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/shared-infrastructure b/shared-infrastructure index 6c52486c..9a6cf00d 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 6c52486c512357475cbb92bbb7c4c0af4d85b1db +Subproject commit 9a6cf00d9a3d482bb08211dd8309f4724a2735cb diff --git a/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs b/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs index 772cc42d..e311ae86 100644 --- a/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs +++ b/src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs @@ -18,6 +18,7 @@ internal static class AmazonS3ClientFactory /// /// A new . /// + /// Invalid configuration. public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options) { if (!string.IsNullOrWhiteSpace(options.Endpoint)) @@ -33,14 +34,14 @@ public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options) { // AccessSecret can be empty. Guard.NotNullOrWhiteSpace(options.Region, nameof(options.Region)); - var region = RegionEndpoint.GetBySystemName(options.Region); + RegionEndpoint 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); + RegionEndpoint region = RegionEndpoint.GetBySystemName(options.Region); AmazonS3Config config = new() { RegionEndpoint = region, UseAccelerateEndpoint = options.UseAccelerateEndpoint }; SetTimeout(config, options.Timeout); return new AmazonS3Client(config); diff --git a/src/ImageSharp.Web/AsyncHelper.cs b/src/ImageSharp.Web/AsyncHelper.cs index 8bc52d93..3ba68fcb 100644 --- a/src/ImageSharp.Web/AsyncHelper.cs +++ b/src/ImageSharp.Web/AsyncHelper.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; diff --git a/src/ImageSharp.Web/Caching/HexEncoder.cs b/src/ImageSharp.Web/Caching/HexEncoder.cs index 7687cc3f..acf72787 100644 --- a/src/ImageSharp.Web/Caching/HexEncoder.cs +++ b/src/ImageSharp.Web/Caching/HexEncoder.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/ImageSharp.Web/Caching/ICacheHash.cs b/src/ImageSharp.Web/Caching/ICacheHash.cs index bf262d0e..26324709 100644 --- a/src/ImageSharp.Web/Caching/ICacheHash.cs +++ b/src/ImageSharp.Web/Caching/ICacheHash.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Caching; diff --git a/src/ImageSharp.Web/Caching/ICacheKey.cs b/src/ImageSharp.Web/Caching/ICacheKey.cs index 2a23d42a..d7d3ae43 100644 --- a/src/ImageSharp.Web/Caching/ICacheKey.cs +++ b/src/ImageSharp.Web/Caching/ICacheKey.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Caching/IImageCache.cs b/src/ImageSharp.Web/Caching/IImageCache.cs index bdd22c6b..1ac8e5cd 100644 --- a/src/ImageSharp.Web/Caching/IImageCache.cs +++ b/src/ImageSharp.Web/Caching/IImageCache.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using SixLabors.ImageSharp.Web.Resolvers; @@ -17,7 +16,7 @@ public interface IImageCache /// /// The cache key. /// The . - Task GetAsync(string key); + Task GetAsync(string key); /// /// Sets the value associated with the specified key. diff --git a/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs b/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs index e3abe213..64b56e0b 100644 --- a/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs +++ b/src/ImageSharp.Web/Caching/LegacyV1CacheKey.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Text; diff --git a/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs b/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs index 8e9e7494..dbb1cf16 100644 --- a/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs +++ b/src/ImageSharp.Web/Caching/LruCache/ConcurrentTLruCache{TKey,TValue}.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Web.Caching; @@ -26,6 +26,7 @@ namespace SixLabors.ImageSharp.Web.Caching; /// 6. When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed. /// internal class ConcurrentTLruCache + where TKey : notnull { private readonly ConcurrentDictionary> dictionary; @@ -97,9 +98,9 @@ public ConcurrentTLruCache(int concurrencyLevel, int capacity, IEqualityComparer /// 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) + public bool TryGet(TKey key, [NotNullWhen(true)] 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); } @@ -111,7 +112,7 @@ public bool TryGet(TKey key, out TValue value) // 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) + private bool GetOrDiscard(LongTickCountLruItem item, out TValue? value) { if (this.policy.ShouldDiscard(item)) { @@ -135,7 +136,7 @@ private bool GetOrDiscard(LongTickCountLruItem item, out TValue va /// 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)) + if (this.TryGet(key, out TValue? value)) { return value; } @@ -164,7 +165,7 @@ public TValue GetOrAdd(TKey key, Func valueFactory) /// A task that represents the asynchronous operation. public async Task GetOrAddAsync(TKey key, Func> valueFactory) { - if (this.TryGet(key, out TValue value)) + if (this.TryGet(key, out TValue? value)) { return value; } @@ -202,7 +203,7 @@ public bool TryRemove(TKey key) // 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 (this.dictionary.TryGetValue(key, out LongTickCountLruItem? existing)) { if (existing.WasRemoved) { @@ -219,7 +220,7 @@ public bool TryRemove(TKey key) existing.WasRemoved = true; } - if (this.dictionary.TryRemove(key, out LongTickCountLruItem removedItem)) + 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 @@ -261,7 +262,7 @@ private void CycleHot() { Interlocked.Decrement(ref this.hotCount); - if (this.hotQueue.TryDequeue(out LongTickCountLruItem item)) + if (this.hotQueue.TryDequeue(out LongTickCountLruItem? item)) { ItemDestination where = this.policy.RouteHot(item); this.Move(item, where); @@ -279,7 +280,7 @@ private void CycleWarm() { Interlocked.Decrement(ref this.warmCount); - if (this.warmQueue.TryDequeue(out LongTickCountLruItem item)) + if (this.warmQueue.TryDequeue(out LongTickCountLruItem? item)) { ItemDestination where = this.policy.RouteWarm(item); @@ -308,7 +309,7 @@ private void CycleCold() { Interlocked.Decrement(ref this.coldCount); - if (this.coldQueue.TryDequeue(out LongTickCountLruItem item)) + if (this.coldQueue.TryDequeue(out LongTickCountLruItem? item)) { ItemDestination where = this.policy.RouteCold(item); @@ -354,7 +355,7 @@ private void Move(LongTickCountLruItem item, ItemDestination where 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) diff --git a/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs b/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs index 7bd0a76d..58cb46eb 100644 --- a/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs +++ b/src/ImageSharp.Web/Caching/LruCache/LongTickCountLruItem{TKey,TValue}.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Caching; diff --git a/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs b/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs index 30f28b4f..d6994ec5 100644 --- a/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs +++ b/src/ImageSharp.Web/Caching/LruCache/TLruLongTicksPolicy{TKey,TValue}.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Diagnostics; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs b/src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs index ff0847e0..34fe94a3 100644 --- a/src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs +++ b/src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -77,17 +76,17 @@ internal static string GetCacheRoot(PhysicalFileSystemCacheOptions cacheOptions, } /// - public Task GetAsync(string key) + public Task GetAsync(string key) { string path = Path.Combine(this.cacheRootPath, ToFilePath(key, this.cacheFolderDepth)); FileInfo metaFileInfo = new(ToMetaDataFilePath(path)); if (!metaFileInfo.Exists) { - return Task.FromResult(null); + return Task.FromResult(null); } - return Task.FromResult(new PhysicalFileSystemCacheResolver(metaFileInfo, this.formatUtilities)); + return Task.FromResult(new PhysicalFileSystemCacheResolver(metaFileInfo, this.formatUtilities)); } /// @@ -96,10 +95,10 @@ public async Task SetAsync(string key, Stream stream, ImageCacheMetadata metadat string path = Path.Combine(this.cacheRootPath, ToFilePath(key, this.cacheFolderDepth)); string imagePath = this.ToImageFilePath(path, metadata); string metaPath = ToMetaDataFilePath(path); - string directory = Path.GetDirectoryName(path); + string? directory = Path.GetDirectoryName(path); // Ensure cache directory is created before creating files - if (!Directory.Exists(directory)) + if (!Directory.Exists(directory) && directory is not null) { Directory.CreateDirectory(directory); } diff --git a/src/ImageSharp.Web/Caching/PhysicalFileSystemCacheOptions.cs b/src/ImageSharp.Web/Caching/PhysicalFileSystemCacheOptions.cs index ad09f177..ec2d2410 100644 --- a/src/ImageSharp.Web/Caching/PhysicalFileSystemCacheOptions.cs +++ b/src/ImageSharp.Web/Caching/PhysicalFileSystemCacheOptions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Caching; @@ -21,7 +20,7 @@ public class PhysicalFileSystemCacheOptions /// application content files; commonly 'wwwroot'. /// /// - public string CacheRootPath { get; set; } + public string? CacheRootPath { get; set; } /// /// Gets or sets the cache folder name. diff --git a/src/ImageSharp.Web/Caching/SHA256CacheHash.cs b/src/ImageSharp.Web/Caching/SHA256CacheHash.cs index f0d3f16d..7eeae17a 100644 --- a/src/ImageSharp.Web/Caching/SHA256CacheHash.cs +++ b/src/ImageSharp.Web/Caching/SHA256CacheHash.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Buffers; using System.Runtime.CompilerServices; @@ -33,7 +32,7 @@ public SHA256CacheHash(IOptions options) public string Create(string value, uint length) { int byteCount = Encoding.ASCII.GetByteCount(value); - byte[] buffer = null; + byte[]? buffer = null; try { diff --git a/src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs b/src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs index 74ed4c59..ea9f126a 100644 --- a/src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs +++ b/src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Caching/UriAbsoluteLowerInvariantCacheKey.cs b/src/ImageSharp.Web/Caching/UriAbsoluteLowerInvariantCacheKey.cs index 1c78b30f..2e3069d4 100644 --- a/src/ImageSharp.Web/Caching/UriAbsoluteLowerInvariantCacheKey.cs +++ b/src/ImageSharp.Web/Caching/UriAbsoluteLowerInvariantCacheKey.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Caching/UriRelativeCacheKey.cs b/src/ImageSharp.Web/Caching/UriRelativeCacheKey.cs index cefe8dd0..01d08740 100644 --- a/src/ImageSharp.Web/Caching/UriRelativeCacheKey.cs +++ b/src/ImageSharp.Web/Caching/UriRelativeCacheKey.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Caching/UriRelativeLowerInvariantCacheKey.cs b/src/ImageSharp.Web/Caching/UriRelativeLowerInvariantCacheKey.cs index 1071c333..77e423eb 100644 --- a/src/ImageSharp.Web/Caching/UriRelativeLowerInvariantCacheKey.cs +++ b/src/ImageSharp.Web/Caching/UriRelativeLowerInvariantCacheKey.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/CaseHandlingUriBuilder.cs b/src/ImageSharp.Web/CaseHandlingUriBuilder.cs index f257fa08..e3418820 100644 --- a/src/ImageSharp.Web/CaseHandlingUriBuilder.cs +++ b/src/ImageSharp.Web/CaseHandlingUriBuilder.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Buffers; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp.Web/CommandHandling.cs b/src/ImageSharp.Web/CommandHandling.cs index 02f2e19b..2e436ea6 100644 --- a/src/ImageSharp.Web/CommandHandling.cs +++ b/src/ImageSharp.Web/CommandHandling.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Commands/CommandCollection.cs b/src/ImageSharp.Web/Commands/CommandCollection.cs index 1718384e..16747d48 100644 --- a/src/ImageSharp.Web/Commands/CommandCollection.cs +++ b/src/ImageSharp.Web/Commands/CommandCollection.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Web.Commands; @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Web.Commands; /// /// Represents an ordered collection of processing commands. /// -public sealed class CommandCollection : KeyedCollection> +public sealed class CommandCollection : KeyedCollection> { /// /// Initializes a new instance of the class. @@ -32,7 +32,7 @@ public IEnumerable Keys { get { - foreach (KeyValuePair item in this) + foreach (KeyValuePair item in this) { yield return this.GetKeyForItem(item); } @@ -54,7 +54,7 @@ public IEnumerable Keys { get { - if (!this.TryGetValue(key, out string value)) + if (!this.TryGetValue(key, out string? value)) { ThrowKeyNotFound(); } @@ -64,7 +64,7 @@ public IEnumerable Keys set { - if (this.TryGetValue(key, out KeyValuePair item)) + if (this.TryGetValue(key, out KeyValuePair item)) { this.SetItem(this.IndexOf(item), new(key, value)); } @@ -107,12 +107,12 @@ public IEnumerable Keys /// an element with the specified key; otherwise, . /// /// is null. - public bool TryGetValue(string key, out string value) + public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { - if (this.TryGetValue(key, out KeyValuePair keyValue)) + if (this.TryGetValue(key, out KeyValuePair keyValue)) { value = keyValue.Value; - return true; + return value is not null; } value = default; @@ -138,7 +138,7 @@ public int FindIndex(Predicate match) Guard.NotNull(match, nameof(match)); int index = 0; - foreach (KeyValuePair item in this) + foreach (KeyValuePair item in this) { if (match(item.Key)) { @@ -162,7 +162,7 @@ public int FindIndex(Predicate match) /// public int IndexOf(string key) { - if (this.TryGetValue(key, out KeyValuePair item)) + if (this.TryGetValue(key, out KeyValuePair item)) { return this.IndexOf(item); } @@ -172,8 +172,9 @@ public int IndexOf(string key) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override string GetKeyForItem(KeyValuePair item) => item.Key; + protected override string GetKeyForItem(KeyValuePair item) => item.Key; [MethodImpl(MethodImplOptions.NoInlining)] + [DoesNotReturn] private static void ThrowKeyNotFound() => throw new KeyNotFoundException(); } diff --git a/src/ImageSharp.Web/Commands/CommandCollectionExtensions.cs b/src/ImageSharp.Web/Commands/CommandCollectionExtensions.cs index 6d6752ca..126c990a 100644 --- a/src/ImageSharp.Web/Commands/CommandCollectionExtensions.cs +++ b/src/ImageSharp.Web/Commands/CommandCollectionExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Commands; @@ -15,9 +14,9 @@ public static class CommandCollectionExtensions /// The collection instance. /// The key of the value to get. /// The value associated with the specified key or the default value. - public static string GetValueOrDefault(this CommandCollection collection, string key) + public static string? GetValueOrDefault(this CommandCollection collection, string key) { - collection.TryGetValue(key, out KeyValuePair result); + collection.TryGetValue(key, out KeyValuePair result); return result.Value; } } diff --git a/src/ImageSharp.Web/Commands/CommandParser.cs b/src/ImageSharp.Web/Commands/CommandParser.cs index e527510a..4bd0f534 100644 --- a/src/ImageSharp.Web/Commands/CommandParser.cs +++ b/src/ImageSharp.Web/Commands/CommandParser.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net; using System.Runtime.CompilerServices; @@ -36,12 +36,12 @@ public CommandParser(IEnumerable converters) /// /// The converted instance or the default. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T ParseValue(string value, CultureInfo culture) + public T? ParseValue(string? value, CultureInfo culture) { DebugGuard.NotNull(culture, nameof(culture)); Type type = typeof(T); - ICommandConverter converter = Array.Find(this.converters, x => x.Type.Equals(type)); + ICommandConverter? converter = Array.Find(this.converters, x => x.Type.Equals(type)); if (converter != null) { @@ -59,7 +59,7 @@ public T ParseValue(string value, CultureInfo culture) converter = Array.Find(this.converters, x => x.Type.Equals(typeof(Enum))); if (converter != null) { - return (T)((ICommandConverter)converter).ConvertFrom( + return (T?)((ICommandConverter)converter).ConvertFrom( this, culture, WebUtility.UrlDecode(value), @@ -67,13 +67,10 @@ public T ParseValue(string value, CultureInfo culture) } } - // We don't actually return here. - // The compiler just cannot see our exception. - ThrowNotSupported(type); - return default; + return ThrowNotSupported(type); } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowNotSupported(Type type) + [DoesNotReturn] + private static T ThrowNotSupported(Type type) => throw new NotSupportedException($"Cannot convert to {type.FullName}."); } diff --git a/src/ImageSharp.Web/Commands/Converters/ArrayConverter{T}.cs b/src/ImageSharp.Web/Commands/Converters/ArrayConverter{T}.cs index 0616e18e..426da7e3 100644 --- a/src/ImageSharp.Web/Commands/Converters/ArrayConverter{T}.cs +++ b/src/ImageSharp.Web/Commands/Converters/ArrayConverter{T}.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Runtime.CompilerServices; @@ -21,7 +20,7 @@ public sealed class ArrayConverter : ICommandConverter public T[] ConvertFrom( CommandParser parser, CultureInfo culture, - string value, + string? value, Type propertyType) { if (string.IsNullOrWhiteSpace(value)) @@ -29,11 +28,11 @@ public T[] ConvertFrom( return Array.Empty(); } - var result = new List(); + List result = new(); foreach (string pill in GetStringArray(value, culture)) { - T item = parser.ParseValue(pill, culture); - if (item != null) + T? item = parser.ParseValue(pill, culture); + if (item is not null) { result.Add(item); } @@ -45,7 +44,10 @@ public T[] ConvertFrom( [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string[] GetStringArray(string input, CultureInfo culture) { - char separator = culture.TextInfo.ListSeparator[0]; + char separator = ConverterUtility.GetListSeparator(culture); + + // TODO: Can we use StringSplit Enumerator here? + // https://github.com/dotnet/runtime/issues/934 return input.Split(separator).Select(s => s.Trim()).ToArray(); } } diff --git a/src/ImageSharp.Web/Commands/Converters/ColorConverter.cs b/src/ImageSharp.Web/Commands/Converters/ColorConverter.cs index eba6b37e..31a1fd67 100644 --- a/src/ImageSharp.Web/Commands/Converters/ColorConverter.cs +++ b/src/ImageSharp.Web/Commands/Converters/ColorConverter.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Reflection; @@ -18,7 +17,7 @@ public sealed class ColorConverter : ICommandConverter /// The web color hexadecimal regex. Matches strings arranged /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. /// - private static readonly Regex HexColorRegex = new("([0-9a-fA-F]{3}){1,2}", RegexOptions.Compiled); + private static readonly Regex HexColorRegex = new("([0-9a-fA-F][^,;.-]\\B{3}){1,2}", RegexOptions.Compiled); /// /// The number color regex. @@ -35,16 +34,22 @@ public sealed class ColorConverter : ICommandConverter /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Color ConvertFrom(CommandParser parser, CultureInfo culture, string value, Type propertyType) + public Color ConvertFrom(CommandParser parser, CultureInfo culture, string? value, Type propertyType) { if (string.IsNullOrWhiteSpace(value)) { return default; } - // Numeric r,g,b - r,g,b,a - char separator = culture.TextInfo.ListSeparator[0]; + // Named colors + IDictionary table = ColorConstantsTable.Value; + if (table.TryGetValue(value, out Color color)) + { + return color; + } + // Numeric r,g,b - r,g,b,a + char separator = ConverterUtility.GetListSeparator(culture); if (value.IndexOf(separator) != -1) { string[] components = value.Split(separator); @@ -60,9 +65,9 @@ public Color ConvertFrom(CommandParser parser, CultureInfo culture, string value if (convert) { - List rgba = parser.ParseValue>(value, culture); + List? rgba = parser.ParseValue>(value, culture); - return rgba.Count switch + return rgba?.Count switch { 4 => Color.FromRgba(rgba[0], rgba[1], rgba[2], rgba[3]), 3 => Color.FromRgb(rgba[0], rgba[1], rgba[2]), @@ -77,9 +82,7 @@ public Color ConvertFrom(CommandParser parser, CultureInfo culture, string value return Color.ParseHex(value); } - // Named colors - IDictionary table = ColorConstantsTable.Value; - return table.TryGetValue(value, out Color color) ? color : default; + return default; } private static IDictionary InitializeColorConstantsTable() @@ -90,7 +93,7 @@ private static IDictionary InitializeColorConstantsTable() { if (field.FieldType == typeof(Color)) { - table[field.Name] = (Color)field.GetValue(null); + table[field.Name] = (Color)field.GetValue(null)!; } } diff --git a/src/ImageSharp.Web/Commands/Converters/ConverterUtility.cs b/src/ImageSharp.Web/Commands/Converters/ConverterUtility.cs new file mode 100644 index 00000000..c924e35c --- /dev/null +++ b/src/ImageSharp.Web/Commands/Converters/ConverterUtility.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Web.Commands.Converters; +internal static class ConverterUtility +{ + /// + /// Gets the that is used by the given culture to separate items in a list. + /// + /// The culture. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char GetListSeparator(CultureInfo culture) + => MemoryMarshal.GetReference(culture.TextInfo.ListSeparator); +} diff --git a/src/ImageSharp.Web/Commands/Converters/EnumConverter.cs b/src/ImageSharp.Web/Commands/Converters/EnumConverter.cs index 764a68a3..b74ae4d5 100644 --- a/src/ImageSharp.Web/Commands/Converters/EnumConverter.cs +++ b/src/ImageSharp.Web/Commands/Converters/EnumConverter.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Runtime.CompilerServices; @@ -22,10 +21,10 @@ public sealed class EnumConverter : ICommandConverter /// This allows us to reuse the same converter for infinite enum types. /// [MethodImpl(MethodImplOptions.NoInlining)] - public object ConvertFrom( + public object? ConvertFrom( CommandParser parser, CultureInfo culture, - string value, + string? value, Type propertyType) { if (string.IsNullOrWhiteSpace(value)) @@ -35,7 +34,7 @@ public object ConvertFrom( try { - char separator = culture.TextInfo.ListSeparator[0]; + char separator = ConverterUtility.GetListSeparator(culture); if (value.IndexOf(separator) != -1) { long convertedValue = 0; @@ -64,5 +63,8 @@ public object ConvertFrom( /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string[] GetStringArray(string input, char separator) + + // TODO: Can we use StringSplit Enumerator here? + // https://github.com/dotnet/runtime/issues/934 => input.Split(separator).Select(s => s.Trim()).ToArray(); } diff --git a/src/ImageSharp.Web/Commands/Converters/ICommandConverter.cs b/src/ImageSharp.Web/Commands/Converters/ICommandConverter.cs index 7b7e750a..f81c75e5 100644 --- a/src/ImageSharp.Web/Commands/Converters/ICommandConverter.cs +++ b/src/ImageSharp.Web/Commands/Converters/ICommandConverter.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; @@ -35,5 +34,5 @@ public interface ICommandConverter : ICommandConverter /// The to convert. /// The property type that the converter will convert to. /// The conversion cannot be performed. - T ConvertFrom(CommandParser parser, CultureInfo culture, string value, Type propertyType); + T? ConvertFrom(CommandParser parser, CultureInfo culture, string? value, Type propertyType); } diff --git a/src/ImageSharp.Web/Commands/Converters/IntegralNumberConverter{T}.cs b/src/ImageSharp.Web/Commands/Converters/IntegralNumberConverter{T}.cs index 5c49e141..3fb27fb4 100644 --- a/src/ImageSharp.Web/Commands/Converters/IntegralNumberConverter{T}.cs +++ b/src/ImageSharp.Web/Commands/Converters/IntegralNumberConverter{T}.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; @@ -20,7 +19,7 @@ public sealed class IntegralNumberConverter : ICommandConverter public T ConvertFrom( CommandParser parser, CultureInfo culture, - string value, + string? value, Type propertyType) { if (string.IsNullOrWhiteSpace(value) diff --git a/src/ImageSharp.Web/Commands/Converters/ListConverter{T}.cs b/src/ImageSharp.Web/Commands/Converters/ListConverter{T}.cs index 4b2fdd3a..5a1e35ca 100644 --- a/src/ImageSharp.Web/Commands/Converters/ListConverter{T}.cs +++ b/src/ImageSharp.Web/Commands/Converters/ListConverter{T}.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Runtime.CompilerServices; @@ -21,10 +20,10 @@ public sealed class ListConverter : ICommandConverter> public List ConvertFrom( CommandParser parser, CultureInfo culture, - string value, + string? value, Type propertyType) { - var result = new List(); + List result = new(); if (string.IsNullOrWhiteSpace(value)) { return result; @@ -32,8 +31,8 @@ public List ConvertFrom( foreach (string pill in GetStringArray(value, culture)) { - T item = parser.ParseValue(pill, culture); - if (item != null) + T? item = parser.ParseValue(pill, culture); + if (item is not null) { result.Add(item); } @@ -45,7 +44,10 @@ public List ConvertFrom( [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string[] GetStringArray(string input, CultureInfo culture) { - char separator = culture.TextInfo.ListSeparator[0]; + char separator = ConverterUtility.GetListSeparator(culture); + + // TODO: Can we use StringSplit Enumerator here? + // https://github.com/dotnet/runtime/issues/934 return input.Split(separator).Select(s => s.Trim()).ToArray(); } } diff --git a/src/ImageSharp.Web/Commands/Converters/SimpleCommandConverter{T}.cs b/src/ImageSharp.Web/Commands/Converters/SimpleCommandConverter{T}.cs index d02359a9..399e3120 100644 --- a/src/ImageSharp.Web/Commands/Converters/SimpleCommandConverter{T}.cs +++ b/src/ImageSharp.Web/Commands/Converters/SimpleCommandConverter{T}.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Runtime.CompilerServices; @@ -19,10 +18,10 @@ public sealed class SimpleCommandConverter : ICommandConverter /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T ConvertFrom( + public T? ConvertFrom( CommandParser parser, CultureInfo culture, - string value, + string? value, Type propertyType) { if (string.IsNullOrWhiteSpace(value)) @@ -31,7 +30,7 @@ public T ConvertFrom( } Type t = typeof(T); - Type u = Nullable.GetUnderlyingType(t); + Type? u = Nullable.GetUnderlyingType(t); if (u != null) { diff --git a/src/ImageSharp.Web/Commands/IRequestParser.cs b/src/ImageSharp.Web/Commands/IRequestParser.cs index 4d4d418a..73aede92 100644 --- a/src/ImageSharp.Web/Commands/IRequestParser.cs +++ b/src/ImageSharp.Web/Commands/IRequestParser.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; diff --git a/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParser.cs b/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParser.cs index f4215369..6386ee7d 100644 --- a/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParser.cs +++ b/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParser.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; @@ -41,8 +40,8 @@ public CommandCollection ParseRequestCommands(HttpContext context) } StringValues query = queryCollection[QueryKey]; - string requestedPreset = query[query.Count - 1]; - if (this.presets.TryGetValue(requestedPreset, out CommandCollection collection)) + string? requestedPreset = query[^1]; + if (requestedPreset is not null && this.presets.TryGetValue(requestedPreset, out CommandCollection? collection)) { return collection; } @@ -66,7 +65,11 @@ private static CommandCollection ParsePreset(string unparsedPresetValue) foreach (KeyValuePair pair in parsed) { // Use the indexer for both set and query. This replaces any previously parsed values. - transformed[pair.Key] = pair.Value[pair.Value.Count - 1]; + string? value = pair.Value[^1]; + if (value is not null) + { + transformed[pair.Key] = value; + } } return transformed; diff --git a/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParserOptions.cs b/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParserOptions.cs index f12934f3..7c40e8a4 100644 --- a/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParserOptions.cs +++ b/src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParserOptions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs b/src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs index 9edf4ea4..420fc019 100644 --- a/src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs +++ b/src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -26,7 +25,11 @@ public CommandCollection ParseRequestCommands(HttpContext context) foreach (KeyValuePair pair in query) { // Use the indexer for both set and query. This replaces any previously parsed values. - transformed[pair.Key] = pair.Value[pair.Value.Count - 1]; + string? value = pair.Value[^1]; + if (value is not null) + { + transformed[pair.Key] = value; + } } return transformed; diff --git a/src/ImageSharp.Web/Commands/TypeConstants.cs b/src/ImageSharp.Web/Commands/TypeConstants.cs index e7a0d2b6..c6460de5 100644 --- a/src/ImageSharp.Web/Commands/TypeConstants.cs +++ b/src/ImageSharp.Web/Commands/TypeConstants.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/DependencyInjection/ApplicationBuilderExtensions.cs b/src/ImageSharp.Web/DependencyInjection/ApplicationBuilderExtensions.cs index e3bdaf9c..e2f896dc 100644 --- a/src/ImageSharp.Web/DependencyInjection/ApplicationBuilderExtensions.cs +++ b/src/ImageSharp.Web/DependencyInjection/ApplicationBuilderExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Builder; using SixLabors.ImageSharp.Web.Middleware; diff --git a/src/ImageSharp.Web/DependencyInjection/IImageSharpBuilder.cs b/src/ImageSharp.Web/DependencyInjection/IImageSharpBuilder.cs index 34a2701d..f1935af9 100644 --- a/src/ImageSharp.Web/DependencyInjection/IImageSharpBuilder.cs +++ b/src/ImageSharp.Web/DependencyInjection/IImageSharpBuilder.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.Extensions.DependencyInjection; diff --git a/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilder.cs b/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilder.cs index ac44dfe8..58da1137 100644 --- a/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilder.cs +++ b/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilder.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.Extensions.DependencyInjection; diff --git a/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilderExtensions.cs b/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilderExtensions.cs index d696f015..8c4a17bb 100644 --- a/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilderExtensions.cs +++ b/src/ImageSharp.Web/DependencyInjection/ImageSharpBuilderExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -205,7 +204,7 @@ public static IImageSharpBuilder InsertProvider(this IImageSharpBuild public static IImageSharpBuilder RemoveProvider(this IImageSharpBuilder builder) where TProvider : class, IImageProvider { - ServiceDescriptor descriptor = builder.Services.FirstOrDefault(x => x.ServiceType == typeof(IImageProvider) && x.GetImplementationType() == typeof(TProvider)); + ServiceDescriptor? descriptor = builder.Services.FirstOrDefault(x => x.ServiceType == typeof(IImageProvider) && x.GetImplementationType() == typeof(TProvider)); if (descriptor != null) { builder.Services.Remove(descriptor); @@ -264,7 +263,7 @@ public static IImageSharpBuilder AddProcessor(this IImageSharpBuilde public static IImageSharpBuilder RemoveProcessor(this IImageSharpBuilder builder) where TProcessor : class, IImageWebProcessor { - ServiceDescriptor descriptor = builder.Services.FirstOrDefault(x => x.ServiceType == typeof(IImageWebProcessor) && x.GetImplementationType() == typeof(TProcessor)); + ServiceDescriptor? descriptor = builder.Services.FirstOrDefault(x => x.ServiceType == typeof(IImageWebProcessor) && x.GetImplementationType() == typeof(TProcessor)); if (descriptor != null) { builder.Services.Remove(descriptor); @@ -323,7 +322,7 @@ public static IImageSharpBuilder AddConverter(this IImageSharpBuilde public static IImageSharpBuilder RemoveConverter(this IImageSharpBuilder builder) where TConverter : class, ICommandConverter { - ServiceDescriptor descriptor = builder.Services.FirstOrDefault(x => x.ServiceType == typeof(ICommandConverter) && x.GetImplementationType() == typeof(TConverter)); + ServiceDescriptor? descriptor = builder.Services.FirstOrDefault(x => x.ServiceType == typeof(ICommandConverter) && x.GetImplementationType() == typeof(TConverter)); if (descriptor != null) { builder.Services.Remove(descriptor); @@ -374,7 +373,7 @@ public static IImageSharpBuilder Configure(this IImageSharpBuilder bui return builder; } - private static Type GetImplementationType(this ServiceDescriptor descriptor) + private static Type? GetImplementationType(this ServiceDescriptor descriptor) => descriptor.ImplementationType ?? descriptor.ImplementationInstance?.GetType() ?? descriptor.ImplementationFactory?.GetType().GenericTypeArguments[1]; diff --git a/src/ImageSharp.Web/DependencyInjection/ServiceCollectionExtensions.cs b/src/ImageSharp.Web/DependencyInjection/ServiceCollectionExtensions.cs index 31fcd6ac..92729e1f 100644 --- a/src/ImageSharp.Web/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/ImageSharp.Web/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.Extensions.DependencyInjection; using SixLabors.ImageSharp.Web.Caching; diff --git a/src/ImageSharp.Web/ExifOrientationUtilities.cs b/src/ImageSharp.Web/ExifOrientationUtilities.cs index 15e0d336..dab77e8c 100644 --- a/src/ImageSharp.Web/ExifOrientationUtilities.cs +++ b/src/ImageSharp.Web/ExifOrientationUtilities.cs @@ -1,11 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Web; diff --git a/src/ImageSharp.Web/FormatUtilities.cs b/src/ImageSharp.Web/FormatUtilities.cs index 174950b8..4e9cba52 100644 --- a/src/ImageSharp.Web/FormatUtilities.cs +++ b/src/ImageSharp.Web/FormatUtilities.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Options; @@ -51,7 +51,7 @@ public FormatUtilities(IOptions options) /// if the uri contains an extension; otherwise, . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetExtensionFromUri(string uri, out string extension) + public bool TryGetExtensionFromUri(string uri, [NotNullWhen(true)] out string? extension) { extension = null; int query = uri.IndexOf('?'); @@ -60,7 +60,7 @@ public bool TryGetExtensionFromUri(string uri, out string extension) if (query > -1) { if (uri.Contains(FormatWebProcessor.Format, StringComparison.OrdinalIgnoreCase) - && QueryHelpers.ParseQuery(uri.Substring(query)).TryGetValue(FormatWebProcessor.Format, out StringValues ext)) + && QueryHelpers.ParseQuery(uri[query..]).TryGetValue(FormatWebProcessor.Format, out StringValues ext)) { // We have a query but is it a valid one? ReadOnlySpan extSpan = ext[0].AsSpan(); @@ -86,7 +86,7 @@ public bool TryGetExtensionFromUri(string uri, out string extension) int extensionIndex; if ((extensionIndex = path.LastIndexOf('.')) != -1) { - ReadOnlySpan pathExtension = path.Slice(extensionIndex + 1); + ReadOnlySpan pathExtension = path[(extensionIndex + 1)..]; foreach (string e in this.extensions) { diff --git a/src/ImageSharp.Web/FormattedImage.cs b/src/ImageSharp.Web/FormattedImage.cs index 4a270918..65c82df2 100644 --- a/src/ImageSharp.Web/FormattedImage.cs +++ b/src/ImageSharp.Web/FormattedImage.cs @@ -1,12 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Web; @@ -41,14 +39,15 @@ private FormattedImage(Image image, IImageFormat format, bool keepOpen) { this.Image = image; this.imageFormatsManager = image.GetConfiguration().ImageFormatsManager; - this.Format = format; + this.format = format; + this.encoder = this.imageFormatsManager.GetEncoder(format); this.keepOpen = keepOpen; } /// /// Gets the decoded image. /// - public Image Image { get; private set; } + public Image Image { get; } /// /// Gets or sets the format. @@ -64,7 +63,7 @@ public IImageFormat Format } this.format = value; - this.encoder = this.imageFormatsManager.FindEncoder(value); + this.encoder = this.imageFormatsManager.GetEncoder(value); } } @@ -82,7 +81,7 @@ public IImageEncoder Encoder } // The given type should match the format encoder. - IImageEncoder reference = this.imageFormatsManager.FindEncoder(this.Format); + IImageEncoder reference = this.imageFormatsManager.GetEncoder(this.Format); if (reference.GetType() != value.GetType()) { ThrowInvalid(nameof(value)); @@ -96,26 +95,29 @@ public IImageEncoder Encoder /// Create a new instance of the class from the given stream. /// /// The pixel format. - /// The configuration. + /// The general decoder options. /// The source. /// A representing the asynchronous operation. - internal static async Task LoadAsync(Configuration configuration, Stream source) + internal static async Task LoadAsync(DecoderOptions options, Stream source) where TPixel : unmanaged, IPixel { - (Image image, IImageFormat format) = await Image.LoadWithFormatAsync(configuration, source); - return new FormattedImage(image, format, false); + // TODO: We want to be able to apply decoder options per request. + // For example. If a resize command has been passed with no extra resampling options + // then we should apply those changes on decode. This will allow memory savings and performance improvements. + Image image = await Image.LoadAsync(options, source); + return new FormattedImage(image, image.Metadata.DecodedImageFormat!, false); } /// /// Create a new instance of the class from the given stream. /// - /// The configuration. + /// The general decoder options. /// The source. /// A representing the asynchronous operation. - internal static async Task LoadAsync(Configuration configuration, Stream source) + internal static async Task LoadAsync(DecoderOptions options, Stream source) { - (Image image, IImageFormat format) = await Image.LoadWithFormatAsync(configuration, source); - return new FormattedImage(image, format, false); + Image image = await Image.LoadAsync(options, source); + return new FormattedImage(image, image.Metadata.DecodedImageFormat!, false); } /// @@ -141,8 +143,7 @@ public bool TryGetExifOrientation(out ushort value) value = ExifOrientationMode.Unknown; if (this.Image.Metadata.ExifProfile != null) { - IExifValue orientation = this.Image.Metadata.ExifProfile.GetValue(ExifTag.Orientation); - if (orientation is null) + if (!this.Image.Metadata.ExifProfile.TryGetValue(ExifTag.Orientation, out IExifValue? orientation)) { return false; } @@ -171,13 +172,12 @@ public void Dispose() if (!this.keepOpen) { this.Image?.Dispose(); - this.Image = null; } } - [MethodImpl(MethodImplOptions.NoInlining)] + [DoesNotReturn] private static void ThrowNull(string name) => throw new ArgumentNullException(name); - [MethodImpl(MethodImplOptions.NoInlining)] + [DoesNotReturn] private static void ThrowInvalid(string name) => throw new ArgumentException(name); } diff --git a/src/ImageSharp.Web/HMACUtilities.cs b/src/ImageSharp.Web/HMACUtilities.cs index eb9d497a..f90d8c52 100644 --- a/src/ImageSharp.Web/HMACUtilities.cs +++ b/src/ImageSharp.Web/HMACUtilities.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Buffers; using System.Runtime.CompilerServices; @@ -70,7 +69,7 @@ public static unsafe string ComputeHMACSHA512(string value, byte[] secret) private static unsafe string CreateHMAC(string value, HMAC hmac) { int byteCount = Encoding.ASCII.GetByteCount(value); - byte[] buffer = null; + byte[]? buffer = null; try { diff --git a/src/ImageSharp.Web/ImageCacheMetadata.cs b/src/ImageSharp.Web/ImageCacheMetadata.cs index 0e1ce20d..d871fd8d 100644 --- a/src/ImageSharp.Web/ImageCacheMetadata.cs +++ b/src/ImageSharp.Web/ImageCacheMetadata.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Runtime.CompilerServices; @@ -98,23 +97,24 @@ public ImageCacheMetadata( public static ImageCacheMetadata FromDictionary(IDictionary dictionary) { // DateTime.TryParse(null) == DateTime.MinValue so no need for conditional; - dictionary.TryGetValue(SourceLastModifiedKey, out string sourceLastWriteTimeUtcString); + dictionary.TryGetValue(SourceLastModifiedKey, out string? sourceLastWriteTimeUtcString); DateTime.TryParse(sourceLastWriteTimeUtcString, null, DateTimeStyles.RoundtripKind, out DateTime sourceLastWriteTimeUtc); - dictionary.TryGetValue(CacheLastModifiedKey, out string cacheLastWriteTimeUtcString); + dictionary.TryGetValue(CacheLastModifiedKey, out string? cacheLastWriteTimeUtcString); DateTime.TryParse(cacheLastWriteTimeUtcString, null, DateTimeStyles.RoundtripKind, out DateTime cacheLastWriteTimeUtc); - dictionary.TryGetValue(ContentTypeKey, out string contentType); + dictionary.TryGetValue(ContentTypeKey, out string? contentType); + Guard.NotNull(contentType); // int.TryParse(null) == 0 and we want to return TimeSpan.MinValue not TimeSpan.Zero TimeSpan cacheControlMaxAge = TimeSpan.MinValue; - if (dictionary.TryGetValue(CacheControlKey, out string cacheControlMaxAgeString)) + if (dictionary.TryGetValue(CacheControlKey, out string? cacheControlMaxAgeString)) { _ = int.TryParse(cacheControlMaxAgeString, out int maxAge); cacheControlMaxAge = TimeSpan.FromSeconds(maxAge); } - dictionary.TryGetValue(ContentLengthKey, out string contentLengthString); + dictionary.TryGetValue(ContentLengthKey, out string? contentLengthString); _ = long.TryParse(contentLengthString, out long contentLength); return new ImageCacheMetadata( @@ -135,7 +135,7 @@ public static async Task ReadAsync(Stream stream) Dictionary dictionary = new(); using (StreamReader reader = new(stream, Encoding.UTF8)) { - string line; + string? line; while ((line = await reader.ReadLineAsync()) != null) { int idx = line.IndexOf(':'); @@ -160,7 +160,7 @@ public bool Equals(ImageCacheMetadata other) && this.ContentLength == other.ContentLength; /// - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is ImageCacheMetadata data && this.Equals(data); /// diff --git a/src/ImageSharp.Web/ImageMetadata.cs b/src/ImageSharp.Web/ImageMetadata.cs index 9ff37df7..ba2d5462 100644 --- a/src/ImageSharp.Web/ImageMetadata.cs +++ b/src/ImageSharp.Web/ImageMetadata.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Runtime.CompilerServices; @@ -90,7 +89,7 @@ public bool Equals(ImageMetadata other) && this.ContentLength == other.ContentLength; /// - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is ImageMetadata data && this.Equals(data); /// diff --git a/src/ImageSharp.Web/ImageSharp.Web.csproj b/src/ImageSharp.Web/ImageSharp.Web.csproj index 26986650..a4f69af8 100644 --- a/src/ImageSharp.Web/ImageSharp.Web.csproj +++ b/src/ImageSharp.Web/ImageSharp.Web.csproj @@ -45,7 +45,7 @@ - + diff --git a/src/ImageSharp.Web/ImageSharpRequestAuthorizationUtilities.cs b/src/ImageSharp.Web/ImageSharpRequestAuthorizationUtilities.cs index 18a5c502..7c1aede5 100644 --- a/src/ImageSharp.Web/ImageSharpRequestAuthorizationUtilities.cs +++ b/src/ImageSharp.Web/ImageSharpRequestAuthorizationUtilities.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.AspNetCore.Http; @@ -96,7 +95,7 @@ public void StripUnknownCommands(CommandCollection commands) /// The uri to compute the code from. /// The command collection handling. /// The computed HMAC. - public string ComputeHMAC(string uri, CommandHandling handling) + public string? ComputeHMAC(string uri, CommandHandling handling) => this.ComputeHMAC(new Uri(uri, UriKind.RelativeOrAbsolute), handling); /// @@ -105,7 +104,7 @@ public string ComputeHMAC(string uri, CommandHandling handling) /// The uri to compute the code from. /// The command collection handling. /// The computed HMAC. - public Task ComputeHMACAsync(string uri, CommandHandling handling) + public Task ComputeHMACAsync(string uri, CommandHandling handling) => this.ComputeHMACAsync(new Uri(uri, UriKind.RelativeOrAbsolute), handling); /// @@ -114,7 +113,7 @@ public Task ComputeHMACAsync(string uri, CommandHandling handling) /// The uri to compute the code from. /// The command collection handling. /// The computed HMAC. - public string ComputeHMAC(Uri uri, CommandHandling handling) + public string? ComputeHMAC(Uri uri, CommandHandling handling) { ToComponents( uri, @@ -131,7 +130,7 @@ public string ComputeHMAC(Uri uri, CommandHandling handling) /// The uri to compute the code from. /// The command collection handling. /// The computed HMAC. - public Task ComputeHMACAsync(Uri uri, CommandHandling handling) + public Task ComputeHMACAsync(Uri uri, CommandHandling handling) { ToComponents( uri, @@ -150,7 +149,7 @@ public Task ComputeHMACAsync(Uri uri, CommandHandling handling) /// The querystring. /// The command collection handling. /// The computed HMAC. - public string ComputeHMAC(HostString host, PathString path, QueryString queryString, CommandHandling handling) + public string? ComputeHMAC(HostString host, PathString path, QueryString queryString, CommandHandling handling) => this.ComputeHMAC(host, path, queryString, new(QueryHelpers.ParseQuery(queryString.Value)), handling); /// @@ -161,7 +160,7 @@ public string ComputeHMAC(HostString host, PathString path, QueryString queryStr /// The querystring. /// The command collection handling. /// The computed HMAC. - public Task ComputeHMACAsync(HostString host, PathString path, QueryString queryString, CommandHandling handling) + public Task ComputeHMACAsync(HostString host, PathString path, QueryString queryString, CommandHandling handling) => this.ComputeHMACAsync(host, path, queryString, new(QueryHelpers.ParseQuery(queryString.Value)), handling); /// @@ -173,7 +172,7 @@ public Task ComputeHMACAsync(HostString host, PathString path, QueryStri /// The query collection. /// The command collection handling. /// The computed HMAC. - public string ComputeHMAC(HostString host, PathString path, QueryString queryString, QueryCollection query, CommandHandling handling) + public string? ComputeHMAC(HostString host, PathString path, QueryString queryString, QueryCollection query, CommandHandling handling) => this.ComputeHMAC(this.ToHttpContext(host, path, queryString, query), handling); /// @@ -185,7 +184,7 @@ public string ComputeHMAC(HostString host, PathString path, QueryString queryStr /// The query collection. /// The command collection handling. /// The computed HMAC. - public Task ComputeHMACAsync(HostString host, PathString path, QueryString queryString, QueryCollection query, CommandHandling handling) + public Task ComputeHMACAsync(HostString host, PathString path, QueryString queryString, QueryCollection query, CommandHandling handling) => this.ComputeHMACAsync(this.ToHttpContext(host, path, queryString, query), handling); /// @@ -194,7 +193,7 @@ public Task ComputeHMACAsync(HostString host, PathString path, QueryStri /// The request HTTP context. /// The command collection handling. /// The computed HMAC. - public string ComputeHMAC(HttpContext context, CommandHandling handling) + public string? ComputeHMAC(HttpContext context, CommandHandling handling) => AsyncHelper.RunSync(() => this.ComputeHMACAsync(context, handling)); /// @@ -203,7 +202,7 @@ public string ComputeHMAC(HttpContext context, CommandHandling handling) /// The request HTTP context. /// The command collection handling. /// The computed HMAC. - public async Task ComputeHMACAsync(HttpContext context, CommandHandling handling) + public async Task ComputeHMACAsync(HttpContext context, CommandHandling handling) { byte[] secret = this.options.HMACSecretKey; if (secret is null || secret.Length == 0) diff --git a/src/ImageSharp.Web/Middleware/ImageCommandContext.cs b/src/ImageSharp.Web/Middleware/ImageCommandContext.cs index 0bb06af9..228347f0 100644 --- a/src/ImageSharp.Web/Middleware/ImageCommandContext.cs +++ b/src/ImageSharp.Web/Middleware/ImageCommandContext.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.AspNetCore.Http; diff --git a/src/ImageSharp.Web/Middleware/ImageContext.cs b/src/ImageSharp.Web/Middleware/ImageContext.cs index f34058b1..697dc5e7 100644 --- a/src/ImageSharp.Web/Middleware/ImageContext.cs +++ b/src/ImageSharp.Web/Middleware/ImageContext.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -23,7 +22,7 @@ internal struct ImageContext private DateTimeOffset fileLastModified; private long fileLength; - private EntityTagHeaderValue fileEtag; + private EntityTagHeaderValue? fileEtag; private PreconditionState ifMatchState; private PreconditionState ifNoneMatchState; diff --git a/src/ImageSharp.Web/Middleware/ImageProcessingContext.cs b/src/ImageSharp.Web/Middleware/ImageProcessingContext.cs index a03a5508..53c0c3d1 100644 --- a/src/ImageSharp.Web/Middleware/ImageProcessingContext.cs +++ b/src/ImageSharp.Web/Middleware/ImageProcessingContext.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Commands; diff --git a/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs b/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs index d964f218..9b9e9b2b 100644 --- a/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs +++ b/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Diagnostics; using System.Globalization; @@ -10,7 +9,6 @@ using Microsoft.Extensions.Options; using Microsoft.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Processors; @@ -34,7 +32,7 @@ private static readonly ConcurrentTLruCache SourceMetadat /// /// Used to temporarily store cache resolver reads to reduce the overhead of cache lookups. /// - private static readonly ConcurrentTLruCache CacheResolverLru + private static readonly ConcurrentTLruCache CacheResolverLru = new(1024, TimeSpan.FromSeconds(30)); /// @@ -187,7 +185,7 @@ public ImageSharpMiddleware( private async Task Invoke(HttpContext httpContext, bool retry) { // Get the correct provider for the request - IImageProvider provider = null; + IImageProvider? provider = null; foreach (IImageProvider resolver in this.providers) { if (resolver.Match(httpContext)) @@ -209,7 +207,7 @@ private async Task Invoke(HttpContext httpContext, bool retry) // First check for a HMAC token and capture before the command is stripped out. byte[] secret = this.options.HMACSecretKey; bool checkHMAC = false; - string token = null; + string? token = null; if (secret?.Length > 0) { checkHMAC = true; @@ -220,7 +218,7 @@ private async Task Invoke(HttpContext httpContext, bool retry) ImageCommandContext imageCommandContext = new(httpContext, commands, this.commandParser, this.parserCulture); // At this point we know that this is an image request so should attempt to compute a validating HMAC. - string hmac = null; + string? hmac = null; if (checkHMAC && token != null) { // Generate and cache a HMAC to validate against based upon the current valid commands from the request. @@ -255,7 +253,7 @@ private async Task Invoke(HttpContext httpContext, bool retry) } } - IImageResolver sourceImageResolver = await provider.GetAsync(httpContext); + IImageResolver? sourceImageResolver = await provider.GetAsync(httpContext); if (sourceImageResolver is null) { @@ -313,7 +311,7 @@ private async Task ProcessRequestAsync( // Enter an asynchronous write worker which prevents multiple writes and delays any reads for the same request. // This reduces the overheads of unnecessary processing. - RecyclableMemoryStream outStream = null; + RecyclableMemoryStream? outStream = null; try { Task takeLockTask = this.asyncKeyLock.WriterLockAsync(key); @@ -347,16 +345,21 @@ private async Task ProcessRequestAsync( using (Stream inStream = await sourceImageResolver.OpenReadAsync()) { + DecoderOptions decoderOptions = new() { Configuration = this.options.Configuration }; + + // TODO: We need some way to set options based upon processors. + await this.options.OnBeforeLoadAsync.Invoke(httpContext, decoderOptions); + // No commands? We simply copy the stream across. if (commands.Count == 0) { await inStream.CopyToAsync(outStream); outStream.Position = 0; - format = await Image.DetectFormatAsync(this.options.Configuration, outStream); + format = await Image.DetectFormatAsync(decoderOptions, outStream); } else { - FormattedImage image = null; + FormattedImage? image = null; try { // Now we can finally process the image. @@ -370,11 +373,11 @@ private async Task ProcessRequestAsync( if (requiresAlpha) { - image = await FormattedImage.LoadAsync(this.options.Configuration, inStream); + image = await FormattedImage.LoadAsync(decoderOptions, inStream); } else { - image = await FormattedImage.LoadAsync(this.options.Configuration, inStream); + image = await FormattedImage.LoadAsync(decoderOptions, inStream); } image.Process( @@ -438,7 +441,7 @@ private async Task ProcessRequestAsync( } } - private static ValueTask StreamDisposeAsync(Stream stream) + private static ValueTask StreamDisposeAsync(Stream? stream) { if (stream is null) { @@ -460,12 +463,12 @@ private async Task IsNewOrUpdatedAsync( // Check to see if the cache contains this image. // If not, we return early. No further checks necessary. - (IImageCacheResolver ImageCacheResolver, ImageCacheMetadata ImageCacheMetadata) cachedImage = await + (IImageCacheResolver? ImageCacheResolver, ImageCacheMetadata ImageCacheMetadata) cachedImage = await CacheResolverLru.GetOrAddAsync( key, async k => { - IImageCacheResolver resolver = await this.cache.GetAsync(k); + IImageCacheResolver? resolver = await this.cache.GetAsync(k); ImageCacheMetadata metadata = default; if (resolver != null) { @@ -503,8 +506,8 @@ private async Task SendResponseAsync( ImageContext imageContext, string key, ImageCacheMetadata metadata, - IImageCacheResolver cacheResolver, - Stream stream, + IImageCacheResolver? cacheResolver, + Stream? stream, bool retry) { imageContext.ComprehendRequestHeaders(metadata.CacheLastWriteTimeUtc, metadata.ContentLength); @@ -531,6 +534,8 @@ private async Task SendResponseAsync( { try { + Guard.NotNull(cacheResolver); + using Stream cacheStream = await cacheResolver.OpenReadAsync(); await imageContext.SendAsync(cacheStream, metadata); } diff --git a/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs b/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs index ebb294e8..a1753f35 100644 --- a/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs +++ b/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Providers; @@ -27,6 +27,7 @@ public class ImageSharpMiddlewareOptions }; private Func onParseCommandsAsync = _ => Task.CompletedTask; + private Func onBeforeLoadAsync = (_, _) => Task.CompletedTask; private Func onBeforeSaveAsync = _ => Task.CompletedTask; private Func onProcessedAsync = _ => Task.CompletedTask; private Func onPrepareResponseAsync = _ => Task.CompletedTask; @@ -113,6 +114,21 @@ public Func OnParseCommandsAsync } } + /// + /// Gets or sets the method that can be used to used to augment decoder options. + /// This is called before the image is decoded and loaded for processing. + /// + public Func OnBeforeLoadAsync + { + get => this.onBeforeLoadAsync; + + set + { + Guard.NotNull(value, nameof(this.OnBeforeLoadAsync)); + this.onBeforeLoadAsync = value; + } + } + /// /// Gets or sets the additional method that can be used for final manipulation before the image is saved. /// This is called after image has been processed, but before the image has been saved to the output stream for caching. diff --git a/src/ImageSharp.Web/Middleware/ImageWorkerResult.cs b/src/ImageSharp.Web/Middleware/ImageWorkerResult.cs index f15a66a4..ee25c663 100644 --- a/src/ImageSharp.Web/Middleware/ImageWorkerResult.cs +++ b/src/ImageSharp.Web/Middleware/ImageWorkerResult.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using SixLabors.ImageSharp.Web.Resolvers; @@ -27,7 +26,7 @@ public ImageWorkerResult(ImageMetadata sourceImageMetadata, ImageCacheMetadata c this.Resolver = resolver; } - public ImageWorkerResult(ImageCacheMetadata cacheImageMetadata, IImageCacheResolver resolver) + public ImageWorkerResult(ImageCacheMetadata cacheImageMetadata, IImageCacheResolver? resolver) { this.IsNewOrUpdated = false; this.SourceImageMetadata = default; @@ -41,5 +40,5 @@ public ImageWorkerResult(ImageCacheMetadata cacheImageMetadata, IImageCacheResol public ImageCacheMetadata CacheImageMetadata { get; } - public IImageCacheResolver Resolver { get; } + public IImageCacheResolver? Resolver { get; } } diff --git a/src/ImageSharp.Web/Middleware/LoggerExtensions.cs b/src/ImageSharp.Web/Middleware/LoggerExtensions.cs index 84195f02..7f158e8a 100644 --- a/src/ImageSharp.Web/Middleware/LoggerExtensions.cs +++ b/src/ImageSharp.Web/Middleware/LoggerExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.Extensions.Logging; @@ -12,10 +11,10 @@ namespace SixLabors.ImageSharp.Web.Middleware; internal static class LoggerExtensions { private static readonly Action LogProcessingErrorAction; - private static readonly Action LogResolveFailedAction; - private static readonly Action LogServedAction; - private static readonly Action LogPathNotModifiedAction; - private static readonly Action LogPreconditionFailedAction; + private static readonly Action LogResolveFailedAction; + private static readonly Action LogServedAction; + private static readonly Action LogPathNotModifiedAction; + private static readonly Action LogPreconditionFailedAction; /// /// Initializes static members of the class. diff --git a/src/ImageSharp.Web/PathUtilities.cs b/src/ImageSharp.Web/PathUtilities.cs index e3f6ef6b..719b2e27 100644 --- a/src/ImageSharp.Web/PathUtilities.cs +++ b/src/ImageSharp.Web/PathUtilities.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web; diff --git a/src/ImageSharp.Web/Processors/AutoOrientWebProcessor.cs b/src/ImageSharp.Web/Processors/AutoOrientWebProcessor.cs index 392d58a5..fc081169 100644 --- a/src/ImageSharp.Web/Processors/AutoOrientWebProcessor.cs +++ b/src/ImageSharp.Web/Processors/AutoOrientWebProcessor.cs @@ -1,10 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.Extensions.Logging; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Web.Commands; namespace SixLabors.ImageSharp.Web.Processors; diff --git a/src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs b/src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs index 7fd82474..549fa716 100644 --- a/src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs +++ b/src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs @@ -1,10 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.Extensions.Logging; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Web.Commands; namespace SixLabors.ImageSharp.Web.Processors; diff --git a/src/ImageSharp.Web/Processors/FormatWebProcessor.cs b/src/ImageSharp.Web/Processors/FormatWebProcessor.cs index f8665dc4..f8093018 100644 --- a/src/ImageSharp.Web/Processors/FormatWebProcessor.cs +++ b/src/ImageSharp.Web/Processors/FormatWebProcessor.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.Extensions.Logging; @@ -50,16 +49,12 @@ public FormattedImage Process( CommandParser parser, CultureInfo culture) { - string extension = commands.GetValueOrDefault(Format); + string? extension = commands.GetValueOrDefault(Format); - if (!string.IsNullOrWhiteSpace(extension)) + if (!string.IsNullOrWhiteSpace(extension) + && this.options.Configuration.ImageFormatsManager.TryFindFormatByFileExtension(extension, out IImageFormat? format)) { - IImageFormat format = this.options.Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); - - if (format != null) - { - image.Format = format; - } + image.Format = format; } return image; diff --git a/src/ImageSharp.Web/Processors/IImageWebProcessor.cs b/src/ImageSharp.Web/Processors/IImageWebProcessor.cs index 6e1f8a85..df0cdb22 100644 --- a/src/ImageSharp.Web/Processors/IImageWebProcessor.cs +++ b/src/ImageSharp.Web/Processors/IImageWebProcessor.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.Extensions.Logging; diff --git a/src/ImageSharp.Web/Processors/QualityWebProcessor.cs b/src/ImageSharp.Web/Processors/QualityWebProcessor.cs index 76af983f..37d8e9da 100644 --- a/src/ImageSharp.Web/Processors/QualityWebProcessor.cs +++ b/src/ImageSharp.Web/Processors/QualityWebProcessor.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.Extensions.Logging; @@ -45,24 +44,30 @@ public FormattedImage Process( if (image.Format is JpegFormat) { - var reference = + JpegEncoder reference = (JpegEncoder)image.Image .GetConfiguration() .ImageFormatsManager - .FindEncoder(image.Format); + .GetEncoder(image.Format); if (quality != reference.Quality) { - image.Encoder = new JpegEncoder() { Quality = quality, ColorType = reference.ColorType }; + image.Encoder = new JpegEncoder() + { + Quality = quality, + Interleaved = reference.Interleaved, + ColorType = reference.ColorType, + SkipMetadata = reference.SkipMetadata + }; } } else if (image.Format is WebpFormat) { - var reference = + WebpEncoder reference = (WebpEncoder)image.Image .GetConfiguration() .ImageFormatsManager - .FindEncoder(image.Format); + .GetEncoder(image.Format); image.Encoder = new WebpEncoder() { @@ -76,6 +81,7 @@ public FormattedImage Process( TransparentColorMode = reference.TransparentColorMode, NearLossless = reference.NearLossless, NearLosslessQuality = reference.NearLosslessQuality, + SkipMetadata = reference.SkipMetadata }; } } diff --git a/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs b/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs index 37a53a67..74e504b6 100644 --- a/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs +++ b/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs @@ -1,12 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using System.Numerics; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Web.Commands; @@ -87,7 +85,7 @@ public FormattedImage Process( CommandParser parser, CultureInfo culture) { - ResizeOptions options = GetResizeOptions(image, commands, parser, culture); + ResizeOptions? options = GetResizeOptions(image, commands, parser, culture); if (options != null) { @@ -107,7 +105,7 @@ public FormattedImage Process( /// The to use as the current parsing culture. /// /// The . - internal static ResizeOptions GetResizeOptions( + internal static ResizeOptions? GetResizeOptions( FormattedImage image, CommandCollection commands, CommandParser parser, @@ -127,15 +125,18 @@ internal static ResizeOptions GetResizeOptions( return null; } + ResizeMode mode = GetMode(commands, parser, culture); + return new() { Size = size, CenterCoordinates = GetCenter(orientation, commands, parser, culture), Position = GetAnchor(orientation, commands, parser, culture), - Mode = GetMode(commands, parser, culture), + Mode = mode, Compand = GetCompandMode(commands, parser, culture), Sampler = GetSampler(commands), - PadColor = parser.ParseValue(commands.GetValueOrDefault(Color), culture) + PadColor = parser.ParseValue(commands.GetValueOrDefault(Color), culture), + TargetRectangle = mode is ResizeMode.Manual ? new Rectangle(0, 0, size.Width, size.Height) : null }; } @@ -165,7 +166,12 @@ private static Size ParseSize( CommandParser parser, CultureInfo culture) { - float[] coordinates = parser.ParseValue(commands.GetValueOrDefault(Xy), culture); + float[]? coordinates = parser.ParseValue(commands.GetValueOrDefault(Xy), culture); + + if (coordinates is null) + { + return null; + } if (coordinates.Length != 2) { @@ -200,7 +206,7 @@ private static bool GetCompandMode( private static IResampler GetSampler(CommandCollection commands) { - string sampler = commands.GetValueOrDefault(Sampler); + string? sampler = commands.GetValueOrDefault(Sampler); if (sampler != null) { diff --git a/src/ImageSharp.Web/Processors/WebProcessingExtensions.cs b/src/ImageSharp.Web/Processors/WebProcessingExtensions.cs index 4c568b0a..ada16b3d 100644 --- a/src/ImageSharp.Web/Processors/WebProcessingExtensions.cs +++ b/src/ImageSharp.Web/Processors/WebProcessingExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Globalization; using Microsoft.Extensions.Logging; diff --git a/src/ImageSharp.Web/Providers/FileProviderImageProvider.cs b/src/ImageSharp.Web/Providers/FileProviderImageProvider.cs index c7dc3a5b..96f55441 100644 --- a/src/ImageSharp.Web/Providers/FileProviderImageProvider.cs +++ b/src/ImageSharp.Web/Providers/FileProviderImageProvider.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -51,14 +50,18 @@ public virtual bool IsValidRequest(HttpContext context) => this.formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _); /// - public Task GetAsync(HttpContext context) + public Task GetAsync(HttpContext context) { - IFileInfo fileInfo = this.fileProvider.GetFileInfo(context.Request.Path.Value); - if (!fileInfo.Exists) + string? path = context.Request.Path.Value; + if (path is not null) { - return Task.FromResult(null); + IFileInfo fileInfo = this.fileProvider.GetFileInfo(path); + if (fileInfo.Exists) + { + return Task.FromResult(new FileProviderImageResolver(fileInfo)); + } } - return Task.FromResult(new FileProviderImageResolver(fileInfo)); + return Task.FromResult(null); } } diff --git a/src/ImageSharp.Web/Providers/IImageProvider.cs b/src/ImageSharp.Web/Providers/IImageProvider.cs index a25e7525..6de0c68d 100644 --- a/src/ImageSharp.Web/Providers/IImageProvider.cs +++ b/src/ImageSharp.Web/Providers/IImageProvider.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Http; using SixLabors.ImageSharp.Web.Resolvers; @@ -36,5 +35,5 @@ public interface IImageProvider /// /// The current HTTP request context. /// The . - Task GetAsync(HttpContext context); + Task GetAsync(HttpContext context); } diff --git a/src/ImageSharp.Web/Providers/PhysicalFileSystemProvider.cs b/src/ImageSharp.Web/Providers/PhysicalFileSystemProvider.cs index dddfe2f2..7634e20f 100644 --- a/src/ImageSharp.Web/Providers/PhysicalFileSystemProvider.cs +++ b/src/ImageSharp.Web/Providers/PhysicalFileSystemProvider.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.FileProviders; diff --git a/src/ImageSharp.Web/Providers/PhysicalFileSystemProviderOptions.cs b/src/ImageSharp.Web/Providers/PhysicalFileSystemProviderOptions.cs index f7b6f42e..57aaa99f 100644 --- a/src/ImageSharp.Web/Providers/PhysicalFileSystemProviderOptions.cs +++ b/src/ImageSharp.Web/Providers/PhysicalFileSystemProviderOptions.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Providers; @@ -21,7 +20,7 @@ public class PhysicalFileSystemProviderOptions /// application content files; commonly 'wwwroot'. /// /// - public string ProviderRootPath { get; set; } + public string? ProviderRootPath { get; set; } /// /// Gets or sets the processing behavior. Defaults to . diff --git a/src/ImageSharp.Web/Providers/WebRootImageProvider.cs b/src/ImageSharp.Web/Providers/WebRootImageProvider.cs index 5c928dc3..a469dd11 100644 --- a/src/ImageSharp.Web/Providers/WebRootImageProvider.cs +++ b/src/ImageSharp.Web/Providers/WebRootImageProvider.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.AspNetCore.Hosting; diff --git a/src/ImageSharp.Web/Resolvers/FileProviderImageResolver.cs b/src/ImageSharp.Web/Resolvers/FileProviderImageResolver.cs index af0c6bb0..d4d6f0f1 100644 --- a/src/ImageSharp.Web/Resolvers/FileProviderImageResolver.cs +++ b/src/ImageSharp.Web/Resolvers/FileProviderImageResolver.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using Microsoft.Extensions.FileProviders; diff --git a/src/ImageSharp.Web/Resolvers/IImageCacheResolver.cs b/src/ImageSharp.Web/Resolvers/IImageCacheResolver.cs index 2d2b784a..b629bb86 100644 --- a/src/ImageSharp.Web/Resolvers/IImageCacheResolver.cs +++ b/src/ImageSharp.Web/Resolvers/IImageCacheResolver.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Resolvers; diff --git a/src/ImageSharp.Web/Resolvers/IImageResolver.cs b/src/ImageSharp.Web/Resolvers/IImageResolver.cs index 935702b9..72d6dfb0 100644 --- a/src/ImageSharp.Web/Resolvers/IImageResolver.cs +++ b/src/ImageSharp.Web/Resolvers/IImageResolver.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable namespace SixLabors.ImageSharp.Web.Resolvers; diff --git a/src/ImageSharp.Web/Resolvers/PhysicalFileSystemCacheResolver.cs b/src/ImageSharp.Web/Resolvers/PhysicalFileSystemCacheResolver.cs index 6494079b..ab6f4936 100644 --- a/src/ImageSharp.Web/Resolvers/PhysicalFileSystemCacheResolver.cs +++ b/src/ImageSharp.Web/Resolvers/PhysicalFileSystemCacheResolver.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Web.Caching; diff --git a/tests/ImageSharp.Web.Tests/Processors/AutoOrientWebProcessorTests.cs b/tests/ImageSharp.Web.Tests/Processors/AutoOrientWebProcessorTests.cs index de4b8919..706c8f31 100644 --- a/tests/ImageSharp.Web.Tests/Processors/AutoOrientWebProcessorTests.cs +++ b/tests/ImageSharp.Web.Tests/Processors/AutoOrientWebProcessorTests.cs @@ -26,17 +26,17 @@ public void AutoOrientWebProcessor_UpdatesOrientation() const ushort tl = 1; const ushort br = 3; - using var image = new Image(1, 1); + using Image image = new(1, 1); image.Metadata.ExifProfile = new(); image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, br); - IExifValue orientation = image.Metadata.ExifProfile.GetValue(ExifTag.Orientation); + Assert.True(image.Metadata.ExifProfile.TryGetValue(ExifTag.Orientation, out IExifValue orientation)); Assert.Equal(br, orientation.Value); - using var formatted = new FormattedImage(image, PngFormat.Instance); + using FormattedImage formatted = new(image, PngFormat.Instance); new AutoOrientWebProcessor().Process(formatted, null, commands, parser, culture); - orientation = image.Metadata.ExifProfile.GetValue(ExifTag.Orientation); + Assert.True(image.Metadata.ExifProfile.TryGetValue(ExifTag.Orientation, out orientation)); Assert.Equal(tl, orientation.Value); } } diff --git a/tests/ImageSharp.Web.Tests/Processors/FormattedImageTests.cs b/tests/ImageSharp.Web.Tests/Processors/FormattedImageTests.cs index 66ed89eb..982fb5ea 100644 --- a/tests/ImageSharp.Web.Tests/Processors/FormattedImageTests.cs +++ b/tests/ImageSharp.Web.Tests/Processors/FormattedImageTests.cs @@ -12,8 +12,8 @@ public class FormattedImageTests [Fact] public void ConstructorSetsProperties() { - using var image = new Image(1, 1); - using var formatted = new FormattedImage(image, JpegFormat.Instance); + using Image image = new(1, 1); + using FormattedImage formatted = new(image, JpegFormat.Instance); Assert.NotNull(formatted.Image); Assert.Equal(image, formatted.Image); @@ -28,8 +28,8 @@ public void ConstructorSetsProperties() [Fact] public void CanSetFormat() { - using var image = new Image(1, 1); - using var formatted = new FormattedImage(image, JpegFormat.Instance); + using Image image = new(1, 1); + using FormattedImage formatted = new(image, JpegFormat.Instance); Assert.NotNull(formatted.Format); Assert.Equal(JpegFormat.Instance, formatted.Format); @@ -44,8 +44,8 @@ public void CanSetFormat() [Fact] public void CanSetEncoder() { - using var image = new Image(1, 1); - using var formatted = new FormattedImage(image, PngFormat.Instance); + using Image image = new(1, 1); + using FormattedImage formatted = new(image, PngFormat.Instance); Assert.NotNull(formatted.Format); Assert.Equal(PngFormat.Instance, formatted.Format); @@ -56,14 +56,14 @@ public void CanSetEncoder() formatted.Format = JpegFormat.Instance; Assert.Equal(typeof(JpegEncoder), formatted.Encoder.GetType()); - JpegColorType current = ((JpegEncoder)formatted.Encoder).ColorType.GetValueOrDefault(); + JpegEncodingColor current = ((JpegEncoder)formatted.Encoder).ColorType.GetValueOrDefault(); - Assert.Equal(JpegColorType.YCbCrRatio420, current); - formatted.Encoder = new JpegEncoder { ColorType = JpegColorType.YCbCrRatio444 }; + Assert.Equal(JpegEncodingColor.YCbCrRatio420, current); + formatted.Encoder = new JpegEncoder { ColorType = JpegEncodingColor.YCbCrRatio444 }; - JpegColorType replacement = ((JpegEncoder)formatted.Encoder).ColorType.GetValueOrDefault(); + JpegEncodingColor replacement = ((JpegEncoder)formatted.Encoder).ColorType.GetValueOrDefault(); Assert.NotEqual(current, replacement); - Assert.Equal(JpegColorType.YCbCrRatio444, replacement); + Assert.Equal(JpegEncodingColor.YCbCrRatio444, replacement); } } diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageImageProviderFactory.cs b/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageImageProviderFactory.cs index 5af0560e..bbfb8a5d 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageImageProviderFactory.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageImageProviderFactory.cs @@ -44,7 +44,7 @@ private static async Task InitializeAWSStorageAsync(IServiceProvider services, A { try { - var putBucketRequest = new PutBucketRequest + PutBucketRequest putBucketRequest = new() { BucketName = bucketOptions.BucketName, BucketRegion = bucketOptions.Region, @@ -57,7 +57,7 @@ private static async Task InitializeAWSStorageAsync(IServiceProvider services, A { // CI tests are run in parallel and can sometimes return a // false negative for the existance of a bucket. - if (string.Equals(e.ErrorCode, "BucketAlreadyExists")) + if (string.Equals(e.ErrorCode, "BucketAlreadyExists", StringComparison.Ordinal)) { return; } @@ -84,14 +84,14 @@ private static async Task InitializeAWSStorageAsync(IServiceProvider services, A using Stream stream = file.CreateReadStream(); // Set the max-age property so we get coverage for testing in our AWS provider. - var cacheControl = new CacheControlHeaderValue + CacheControlHeaderValue cacheControl = new() { Public = true, MaxAge = TimeSpan.FromDays(7), MustRevalidate = true }; - var putRequest = new PutObjectRequest() + PutObjectRequest putRequest = new() { BucketName = bucketOptions.BucketName, Key = TestConstants.ImagePath, diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/ServerTestBase.cs b/tests/ImageSharp.Web.Tests/TestUtilities/ServerTestBase.cs index f9fa82a2..6d8b5e83 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/ServerTestBase.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/ServerTestBase.cs @@ -42,7 +42,7 @@ public async Task CanProcessAndResolveImageAsync() string url = this.ImageSource; string ext = Path.GetExtension(url); - IImageFormat format = Configuration.Default.ImageFormatsManager.FindFormatByFileExtension(ext); + Assert.True(Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format)); // First response HttpResponseMessage response = await this.HttpClient.GetAsync(url + await this.AugmentCommandAsync(this.Fixture.Commands[0])); @@ -52,11 +52,10 @@ public async Task CanProcessAndResolveImageAsync() Assert.True(response.Content.Headers.ContentLength > 0); Assert.Equal(format.DefaultMimeType, response.Content.Headers.ContentType.MediaType); - (Image Image, IImageFormat Format) actual = await Image.LoadWithFormatAsync(await response.Content.ReadAsStreamAsync()); - using Image image = actual.Image; + using Image image = await Image.LoadAsync(await response.Content.ReadAsStreamAsync()); Assert.Equal(Width, image.Width); - Assert.Equal(format, actual.Format); + Assert.Equal(format, image.Metadata.DecodedImageFormat); response.Dispose(); @@ -68,16 +67,15 @@ public async Task CanProcessAndResolveImageAsync() Assert.True(response.Content.Headers.ContentLength > 0); Assert.Equal(format.DefaultMimeType, response.Content.Headers.ContentType.MediaType); - (Image Image, IImageFormat Format) cachedActual = await Image.LoadWithFormatAsync(await response.Content.ReadAsStreamAsync()); - using Image cached = cachedActual.Image; + using Image cached = await Image.LoadAsync(await response.Content.ReadAsStreamAsync()); Assert.Equal(Width, cached.Width); - Assert.Equal(format, actual.Format); + Assert.Equal(format, image.Metadata.DecodedImageFormat); response.Dispose(); // 304 response - var request = new HttpRequestMessage + HttpRequestMessage request = new() { RequestUri = new Uri(url + await this.AugmentCommandAsync(this.Fixture.Commands[0])), Method = HttpMethod.Get, @@ -133,7 +131,7 @@ public async Task CanProcessMultipleIdenticalQueriesAsync() Assert.True(response.Content.Headers.ContentLength > 0); })).ToArray(); - var all = Task.WhenAll(tasks); + Task all = Task.WhenAll(tasks); await all; Assert.True(all.IsCompletedSuccessfully); } diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs index bc1ba269..26c64845 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs @@ -8,7 +8,7 @@ public static class TestConstants public const string AzureConnectionString = "UseDevelopmentStorage=true"; public const string AzureContainerName = "azure"; public const string AzureCacheContainerName = "is-cache"; - public const string AWSEndpoint = "http://127.0.0.1:4568/"; + public const string AWSEndpoint = "http://localhost:4568/"; public const string AWSRegion = "eu-west-2"; public const string AWSBucketName = "aws"; public const string AWSCacheBucketName = "aws-cache"; diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/TestServerFixture.cs b/tests/ImageSharp.Web.Tests/TestUtilities/TestServerFixture.cs index 12ed053d..a9f68eed 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/TestServerFixture.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/TestServerFixture.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Middleware; @@ -67,6 +68,16 @@ protected void ConfigureServices(IServiceCollection services) return onParseCommandsAsync.Invoke(context); }; + Func onBeforeLoadAsync = options.OnBeforeLoadAsync; + + options.OnBeforeLoadAsync = (context, decoderOptions) => + { + Assert.NotNull(context); + Assert.NotNull(decoderOptions); + + return onBeforeLoadAsync.Invoke(context, decoderOptions); + }; + Func onProcessedAsync = options.OnProcessedAsync; options.OnProcessedAsync = context =>