11// Copyright (c) Six Labors.
22// Licensed under the Apache License, Version 2.0.
33
4+ using System ;
45using System . IO ;
6+ using System . Runtime . CompilerServices ;
7+ using System . Runtime . InteropServices ;
58using System . Threading . Tasks ;
69using Microsoft . AspNetCore . Hosting ;
710using Microsoft . Extensions . FileProviders ;
@@ -21,6 +24,11 @@ public class PhysicalFileSystemCache : IImageCache
2124 /// </summary>
2225 private readonly string cacheRootPath ;
2326
27+ /// <summary>
28+ /// The length of the filename to use (minus the extension) when storing images in the image cache.
29+ /// </summary>
30+ private readonly int cachedNameLength ;
31+
2432 /// <summary>
2533 /// The file provider abstraction.
2634 /// </summary>
@@ -62,6 +70,7 @@ public PhysicalFileSystemCache(
6270 Guard . NotNull ( options , nameof ( options ) ) ;
6371 Guard . NotNullOrWhiteSpace ( environment . WebRootPath , nameof ( environment . WebRootPath ) ) ;
6472
73+ // Allow configuration of the cache without having to register everything.
6574 this . cacheOptions = cacheOptions != null ? cacheOptions . Value : new PhysicalFileSystemCacheOptions ( ) ;
6675 this . cacheRootPath = Path . Combine ( environment . WebRootPath , this . cacheOptions . CacheFolder ) ;
6776 if ( ! Directory . Exists ( this . cacheRootPath ) )
@@ -71,13 +80,14 @@ public PhysicalFileSystemCache(
7180
7281 this . fileProvider = new PhysicalFileProvider ( this . cacheRootPath ) ;
7382 this . options = options . Value ;
83+ this . cachedNameLength = ( int ) this . options . CachedNameLength ;
7484 this . formatUtilies = formatUtilities ;
7585 }
7686
7787 /// <inheritdoc/>
7888 public async Task < IImageCacheResolver > GetAsync ( string key )
7989 {
80- string path = this . ToFilePath ( key ) ;
90+ string path = ToFilePath ( key , this . cachedNameLength ) ;
8191
8292 IFileInfo metaFileInfo = this . fileProvider . GetFileInfo ( this . ToMetaDataFilePath ( path ) ) ;
8393 if ( ! metaFileInfo . Exists )
@@ -105,7 +115,7 @@ public async Task<IImageCacheResolver> GetAsync(string key)
105115 /// <inheritdoc/>
106116 public async Task SetAsync ( string key , Stream stream , ImageCacheMetadata metadata )
107117 {
108- string path = Path . Combine ( this . cacheRootPath , this . ToFilePath ( key ) ) ;
118+ string path = Path . Combine ( this . cacheRootPath , ToFilePath ( key , this . cachedNameLength ) ) ;
109119 string imagePath = this . ToImageFilePath ( path , metadata ) ;
110120 string metaPath = this . ToMetaDataFilePath ( path ) ;
111121 string directory = Path . GetDirectoryName ( path ) ;
@@ -146,8 +156,35 @@ private string ToImageFilePath(string path, in ImageCacheMetadata metaData)
146156 /// Converts the key into a nested file path.
147157 /// </summary>
148158 /// <param name="key">The cache key.</param>
159+ /// <param name="cachedNameLength">The length of the cached file name minus the extension.</param>
149160 /// <returns>The <see cref="string"/>.</returns>
150- private string ToFilePath ( string key ) // TODO: Avoid the allocation here.
151- => $ "{ string . Join ( "/" , key . Substring ( 0 , ( int ) this . options . CachedNameLength ) . ToCharArray ( ) ) } /{ key } ";
161+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
162+ internal static unsafe string ToFilePath ( string key , int cachedNameLength )
163+ {
164+ // Each key substring char + separator + key
165+ int length = ( cachedNameLength * 2 ) + key . Length ;
166+ fixed ( char * keyPtr = key )
167+ {
168+ return string . Create ( length , ( Ptr : ( IntPtr ) keyPtr , key . Length ) , ( chars , args ) =>
169+ {
170+ const char separator = '/' ;
171+ var keySpan = new ReadOnlySpan < char > ( ( char * ) args . Ptr , args . Length ) ;
172+ ref char keyRef = ref MemoryMarshal . GetReference ( keySpan ) ;
173+ ref char charRef = ref MemoryMarshal . GetReference ( chars ) ;
174+
175+ int index = 0 ;
176+ for ( int i = 0 ; i < cachedNameLength ; i ++ )
177+ {
178+ Unsafe . Add ( ref charRef , index ++ ) = Unsafe. Add ( ref keyRef , i ) ;
179+ Unsafe . Add ( ref charRef , index ++ ) = separator;
180+ }
181+
182+ for ( int i = 0 ; i < keySpan . Length ; i ++ )
183+ {
184+ Unsafe . Add ( ref charRef , index ++ ) = Unsafe. Add ( ref keyRef , i ) ;
185+ }
186+ } ) ;
187+ }
188+ }
152189 }
153190}
0 commit comments