99using Microsoft . AspNetCore . Hosting ;
1010using Microsoft . Extensions . FileProviders ;
1111using Microsoft . Extensions . Options ;
12- using SixLabors . ImageSharp . Web . Middleware ;
1312using SixLabors . ImageSharp . Web . Resolvers ;
1413
1514namespace SixLabors . ImageSharp . Web . Caching
@@ -25,25 +24,15 @@ public class PhysicalFileSystemCache : IImageCache
2524 private readonly string cacheRootPath ;
2625
2726 /// <summary>
28- /// The length of the filename to use (minus the extension) when storing images in the image cache .
27+ /// The depth of the nested cache folders structure to store the images .
2928 /// </summary>
30- private readonly int cachedNameLength ;
29+ private readonly int cacheFolderDepth ;
3130
3231 /// <summary>
3332 /// The file provider abstraction.
3433 /// </summary>
3534 private readonly IFileProvider fileProvider ;
3635
37- /// <summary>
38- /// The cache configuration options.
39- /// </summary>
40- private readonly PhysicalFileSystemCacheOptions cacheOptions ;
41-
42- /// <summary>
43- /// The middleware configuration options.
44- /// </summary>
45- private readonly ImageSharpMiddlewareOptions options ;
46-
4736 /// <summary>
4837 /// Contains various format helper methods based on the current configuration.
4938 /// </summary>
@@ -52,35 +41,31 @@ public class PhysicalFileSystemCache : IImageCache
5241 /// <summary>
5342 /// Initializes a new instance of the <see cref="PhysicalFileSystemCache"/> class.
5443 /// </summary>
55- /// <param name="cacheOptions ">The cache configuration options.</param>
44+ /// <param name="options ">The cache configuration options.</param>
5645 /// <param name="environment">The hosting environment the application is running in.</param>
57- /// <param name="options">The middleware configuration options.</param>
5846 /// <param name="formatUtilities">Contains various format helper methods based on the current configuration.</param>
5947 public PhysicalFileSystemCache (
60- IOptions < PhysicalFileSystemCacheOptions > cacheOptions ,
48+ IOptions < PhysicalFileSystemCacheOptions > options ,
6149#if NETCOREAPP2_1
6250 IHostingEnvironment environment ,
6351#else
6452 IWebHostEnvironment environment ,
6553#endif
66- IOptions < ImageSharpMiddlewareOptions > options ,
6754 FormatUtilities formatUtilities )
6855 {
6956 Guard . NotNull ( environment , nameof ( environment ) ) ;
7057 Guard . NotNull ( options , nameof ( options ) ) ;
7158 Guard . NotNullOrWhiteSpace ( environment . WebRootPath , nameof ( environment . WebRootPath ) ) ;
7259
73- // Allow configuration of the cache without having to register everything.
74- this . cacheOptions = cacheOptions != null ? cacheOptions . Value : new PhysicalFileSystemCacheOptions ( ) ;
75- this . cacheRootPath = GetCacheRoot ( this . cacheOptions , environment . WebRootPath , environment . ContentRootPath ) ;
76- if ( ! Directory . Exists ( this . cacheRootPath ) )
77- {
78- Directory . CreateDirectory ( this . cacheRootPath ) ;
79- }
60+ // Allow configuration of the cache without having to register everything
61+ PhysicalFileSystemCacheOptions cacheOptions = options != null ? options . Value : new PhysicalFileSystemCacheOptions ( ) ;
62+ this . cacheRootPath = GetCacheRoot ( cacheOptions , environment . WebRootPath , environment . ContentRootPath ) ;
63+ this . cacheFolderDepth = ( int ) cacheOptions . CacheFolderDepth ;
64+
65+ // Ensure cache directory is created before initializing the file provider
66+ Directory . CreateDirectory ( this . cacheRootPath ) ;
8067
8168 this . fileProvider = new PhysicalFileProvider ( this . cacheRootPath ) ;
82- this . options = options . Value ;
83- this . cachedNameLength = ( int ) this . options . CachedNameLength ;
8469 this . formatUtilities = formatUtilities ;
8570 }
8671
@@ -105,7 +90,7 @@ internal static string GetCacheRoot(PhysicalFileSystemCacheOptions cacheOptions,
10590 /// <inheritdoc/>
10691 public Task < IImageCacheResolver > GetAsync ( string key )
10792 {
108- string path = ToFilePath ( key , this . cachedNameLength ) ;
93+ string path = ToFilePath ( key , this . cacheFolderDepth ) ;
10994
11095 IFileInfo metaFileInfo = this . fileProvider . GetFileInfo ( this . ToMetaDataFilePath ( path ) ) ;
11196 if ( ! metaFileInfo . Exists )
@@ -119,15 +104,13 @@ public Task<IImageCacheResolver> GetAsync(string key)
119104 /// <inheritdoc/>
120105 public async Task SetAsync ( string key , Stream stream , ImageCacheMetadata metadata )
121106 {
122- string path = Path . Combine ( this . cacheRootPath , ToFilePath ( key , this . cachedNameLength ) ) ;
107+ string path = Path . Combine ( this . cacheRootPath , ToFilePath ( key , this . cacheFolderDepth ) ) ;
123108 string imagePath = this . ToImageFilePath ( path , metadata ) ;
124109 string metaPath = this . ToMetaDataFilePath ( path ) ;
125110 string directory = Path . GetDirectoryName ( path ) ;
126111
127- if ( ! Directory . Exists ( directory ) )
128- {
129- Directory . CreateDirectory ( directory ) ;
130- }
112+ // Ensure cache directory is created before creating files
113+ Directory . CreateDirectory ( directory ) ;
131114
132115 using ( FileStream fileStream = File . Create ( imagePath ) )
133116 {
@@ -162,13 +145,35 @@ private string ToImageFilePath(string path, in ImageCacheMetadata metaData)
162145 /// Converts the key into a nested file path.
163146 /// </summary>
164147 /// <param name="key">The cache key.</param>
165- /// <param name="cachedNameLength">The length of the cached file name minus the extension.</param>
166- /// <returns>The <see cref="string"/>.</returns>
148+ /// <param name="cacheFolderDepth">The depth of the nested cache folders structure to store the images.</param>
149+ /// <returns>
150+ /// The <see cref="string" />.
151+ /// </returns>
167152 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
168- internal static unsafe string ToFilePath ( string key , int cachedNameLength )
153+ internal static unsafe string ToFilePath ( string key , int cacheFolderDepth )
169154 {
170- // Each key substring char + separator + key
171- int length = ( cachedNameLength * 2 ) + key . Length ;
155+ if ( cacheFolderDepth == 0 )
156+ {
157+ // Short-circuit when not nesting folders
158+ return key ;
159+ }
160+
161+ int length ;
162+ int nameStartIndex ;
163+ if ( cacheFolderDepth >= key . Length )
164+ {
165+ // Keep all characters in file name (legacy behavior)
166+ cacheFolderDepth = key . Length ;
167+ length = ( cacheFolderDepth * 2 ) + key . Length ;
168+ nameStartIndex = 0 ;
169+ }
170+ else
171+ {
172+ // Remove characters used in folders from file name
173+ length = cacheFolderDepth + key . Length ;
174+ nameStartIndex = cacheFolderDepth ;
175+ }
176+
172177 fixed ( char * keyPtr = key )
173178 {
174179 return string . Create ( length , ( Ptr : ( IntPtr ) keyPtr , key . Length ) , ( chars , args ) =>
@@ -179,13 +184,13 @@ internal static unsafe string ToFilePath(string key, int cachedNameLength)
179184 ref char charRef = ref MemoryMarshal . GetReference ( chars ) ;
180185
181186 int index = 0 ;
182- for ( int i = 0 ; i < cachedNameLength ; i ++ )
187+ for ( int i = 0 ; i < cacheFolderDepth ; i ++ )
183188 {
184189 Unsafe . Add ( ref charRef , index ++ ) = Unsafe. Add ( ref keyRef , i ) ;
185190 Unsafe . Add ( ref charRef , index ++ ) = separator;
186191 }
187192
188- for ( int i = 0 ; i < keySpan . Length ; i ++ )
193+ for ( int i = nameStartIndex ; i < keySpan . Length ; i ++ )
189194 {
190195 Unsafe . Add ( ref charRef , index ++ ) = Unsafe. Add ( ref keyRef , i ) ;
191196 }
0 commit comments