22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Diagnostics ;
5+ using System . Diagnostics . CodeAnalysis ;
56using System . IO . Compression ;
67using System . Text ;
78using Microsoft . Extensions . Logging ;
@@ -19,13 +20,18 @@ FileSystemCacheProvider parent
1920) : ICacheDirectory
2021{
2122 private ZipArchive ? _committed ;
23+ private bool _hasCommitted ;
2224 private string ? _newPath ;
2325 private ZipArchive ? _new ;
26+ private HashSet < string > ? _newEntries ;
2427 private SemaphoreSlim _sema = new ( 1 , 1 ) ;
2528
2629 private async ValueTask < ZipArchive ? > GetOrCreateCommittedAsync ( )
2730 {
28- if ( ! File . Exists ( committedPath ) || ( Flags & CacheFlags . RequireNew ) == CacheFlags . RequireNew )
31+ if (
32+ ! File . Exists ( committedPath )
33+ || ( Flags & CacheFlags . RequireNew ) == CacheFlags . RequireNew && ! _hasCommitted
34+ )
2935 {
3036 parent . Logger . LogDebug (
3137 "Cache miss with key \" {0}\" ! Expected ZIP archive at {1}" ,
@@ -48,17 +54,22 @@ FileSystemCacheProvider parent
4854 ) ;
4955 }
5056
57+ // [MemberNotNull(nameof(_new))] <-- TODO WHY IS THE COMPILER NOT HAPPY?
58+ [ MemberNotNull ( nameof ( _newEntries ) ) ]
5159 private async ValueTask < ZipArchive > GetOrCreateNewAsync ( )
5260 {
5361 if ( _new is not null )
5462 {
63+ Debug . Assert ( _newEntries is not null ) ;
5564 return _new ;
5665 }
5766
5867 if ( ( Access & FileAccess . Write ) == 0 )
5968 {
6069 CacheUtils . ThrowAccessException ( ) ;
70+ #pragma warning disable CS8774 // Member must have a non-null value when exiting.
6171 return null ! ;
72+ #pragma warning restore CS8774 // Member must have a non-null value when exiting.
6273 }
6374
6475 _newPath = IOPath . GetTempFileName ( ) ;
@@ -67,6 +78,7 @@ private async ValueTask<ZipArchive> GetOrCreateNewAsync()
6778 CacheKey ,
6879 _newPath
6980 ) ;
81+ _newEntries = [ ] ;
7082 return _new = await ZipArchive . CreateAsync (
7183 File . OpenWrite ( _newPath ) ,
7284 ZipArchiveMode . Create ,
@@ -93,7 +105,7 @@ var entry in (await GetOrCreateCommittedAsync())?.Entries
93105 ?? Enumerable . Empty < ZipArchiveEntry > ( )
94106 )
95107 {
96- yield return entry . FullName ;
108+ yield return entry . FullName . ToCacheEntryPath ( ) ;
97109 }
98110 }
99111
@@ -105,12 +117,14 @@ public async ValueTask<Stream> GetCommittedFileAsync(string filePath)
105117 }
106118
107119 parent . Logger . LogTrace ( "Cache hit (\" {0}\" , ZIP): {1}" , CacheKey , filePath ) ;
108- var entry = _committed ? . GetEntry ( filePath ) ?? throw new FileNotFoundException ( ) ;
120+ var entry =
121+ _committed ? . GetEntry ( filePath . ToCacheEntryPath ( ) ) ?? throw new FileNotFoundException ( ) ;
109122 return await entry . OpenAsync ( ) ;
110123 }
111124
112125 public async ValueTask AddFileAsync ( string filePath , Stream stream )
113126 {
127+ filePath = filePath . ToCacheEntryPath ( ) ;
114128 if ( ( Access & FileAccess . Write ) == 0 )
115129 {
116130 CacheUtils . ThrowAccessException ( ) ;
@@ -124,6 +138,7 @@ public async ValueTask AddFileAsync(string filePath, Stream stream)
124138 . CreateEntry ( filePath , CompressionLevel . SmallestSize )
125139 . OpenAsync ( ) ;
126140 await stream . CopyToAsync ( dst ) ;
141+ _newEntries . Add ( filePath ) ;
127142 }
128143 finally
129144 {
@@ -153,7 +168,7 @@ public async ValueTask CommitAsync()
153168 // If the user doesn't want this, they can use RequireNew.
154169 foreach ( var entry in _committed . Entries )
155170 {
156- if ( @new . GetEntry ( entry . FullName ) is null )
171+ if ( ! _newEntries . Contains ( entry . FullName . ToCacheEntryPath ( ) ) )
157172 {
158173 parent . Logger . LogTrace (
159174 "Cache unchanged (\" {0}\" , ZIP): {1}" ,
@@ -181,6 +196,7 @@ public async ValueTask CommitAsync()
181196
182197 await @new . DisposeAsync ( ) ;
183198 _new = null ;
199+ _hasCommitted = true ;
184200 Debug . Assert ( _newPath is not null ) ;
185201 File . Move ( _newPath , committedPath , true ) ;
186202 }
0 commit comments