@@ -27,7 +27,7 @@ public sealed partial class FileDownloadManager : DisposableMediatorSubscriberBa
2727 private readonly FileDownloadNegativeCache _negativeCache ;
2828 private readonly ConcurrentDictionary < ThrottledStream , byte > _activeDownloadStreams = new ( ) ;
2929 private List < DownloadFileTransfer > _currentDownloads = [ ] ;
30- private Dictionary < string , string > _preflightUnavailable = new ( StringComparer . OrdinalIgnoreCase ) ;
30+ private Dictionary < string , FileDownloadNegativeEntry > _preflightUnavailable = new ( StringComparer . OrdinalIgnoreCase ) ;
3131
3232 public FileDownloadManager ( ILogger < FileDownloadManager > logger , SnowMediator mediator ,
3333 FileTransferOrchestrator orchestrator , IFileDownloadTransport transport ,
@@ -69,7 +69,7 @@ public async Task<List<DownloadFileTransfer>> InitiateDownloadList(GameObjectHan
6969 _preflightUnavailable = requestedHashes
7070 . Select ( hash => _negativeCache . TryGet ( hash , out var entry ) ? entry : null )
7171 . Where ( entry => entry != null )
72- . ToDictionary ( entry => entry ! . Hash , entry => entry ! . Message , StringComparer . OrdinalIgnoreCase ) ;
72+ . ToDictionary ( entry => entry ! . Hash , entry => entry ! , StringComparer . OrdinalIgnoreCase ) ;
7373 var eligibleHashes = requestedHashes . Where ( hash => ! _preflightUnavailable . ContainsKey ( hash ) ) . ToList ( ) ;
7474 var fileInfo = eligibleHashes . Count == 0
7575 ? [ ]
@@ -79,22 +79,22 @@ public async Task<List<DownloadFileTransfer>> InitiateDownloadList(GameObjectHan
7979 {
8080 var entry = _negativeCache . Record ( missingHash , FileDownloadNegativeReason . Missing , TimeSpan . FromMinutes ( 10 ) ,
8181 "The requested file is not available on the server. Snowcloak will check again later." ) ;
82- _preflightUnavailable [ entry . Hash ] = entry . Message ;
82+ _preflightUnavailable [ entry . Hash ] = entry ;
8383 }
8484
8585 foreach ( var dto in fileInfo . Where ( file => file . IsForbidden ) )
8686 {
8787 _orchestrator . AddForbiddenTransfer ( new ForbiddenTransfer ( dto . Hash , dto . ForbiddenBy , ForbiddenTransferKind . Download ) ) ;
8888 var entry = _negativeCache . Record ( dto . Hash , FileDownloadNegativeReason . Rejected , TimeSpan . FromMinutes ( 30 ) ,
8989 "The requested file is blocked from transfer." ) ;
90- _preflightUnavailable [ entry . Hash ] = entry . Message ;
90+ _preflightUnavailable [ entry . Hash ] = entry ;
9191 }
9292
9393 foreach ( var dto in fileInfo . Where ( file => ! file . FileExists ) )
9494 {
9595 var entry = _negativeCache . Record ( dto . Hash , FileDownloadNegativeReason . Missing , TimeSpan . FromMinutes ( 10 ) ,
9696 "The requested file is not available on the server. Snowcloak will check again later." ) ;
97- _preflightUnavailable [ entry . Hash ] = entry . Message ;
97+ _preflightUnavailable [ entry . Hash ] = entry ;
9898 }
9999
100100 _currentDownloads = fileInfo . Distinct ( )
@@ -147,10 +147,11 @@ private async Task DownloadFilesInternal(GameObjectHandler gameObjectHandler, IR
147147 var downloadGroups = _currentDownloads . GroupBy ( GetDownloadGroupKey , StringComparer . Ordinal ) . ToList ( ) ;
148148
149149 using var download = _statusStore . Begin ( gameObjectHandler , uid ) ;
150- var unavailable = new ConcurrentDictionary < string , string > ( _preflightUnavailable , StringComparer . OrdinalIgnoreCase ) ;
150+ var unavailable = new ConcurrentDictionary < string , FileDownloadNegativeEntry > ( _preflightUnavailable , StringComparer . OrdinalIgnoreCase ) ;
151151 if ( ! unavailable . IsEmpty )
152152 {
153- var message = unavailable . Values . First ( ) ;
153+ var message = unavailable . Values . FirstOrDefault ( entry => entry . Reason != FileDownloadNegativeReason . Missing ) ? . Message
154+ ?? string . Empty ;
154155 download . AddGroup ( "Unavailable" , 0 , unavailable . Count ) . SetUnavailable ( message ) ;
155156 }
156157 var groupHandles = downloadGroups . ToDictionary ( group => group . Key ,
@@ -185,8 +186,8 @@ await DownloadAndExtractAsync(transfer, expectedExtensionByHash[transfer.Hash],
185186 }
186187 catch ( FileDownloadUnavailableException ex )
187188 {
188- unavailable . TryAdd ( transfer . Hash , ex . Entry . Message ) ;
189- groupHandle . SetUnavailable ( ex . Entry . Message ) ;
189+ unavailable . TryAdd ( transfer . Hash , ex . Entry ) ;
190+ groupHandle . SetUnavailable ( ex . Entry . Reason == FileDownloadNegativeReason . Missing ? string . Empty : ex . Entry . Message ) ;
190191 LogDownloadError ( Logger , ex , transfer . Hash , gameObjectHandler . Name ) ;
191192 }
192193 catch ( Exception ex ) when ( ex is HttpRequestException or IOException or InvalidOperationException or UnauthorizedAccessException )
@@ -201,10 +202,16 @@ await DownloadAndExtractAsync(transfer, expectedExtensionByHash[transfer.Hash],
201202
202203 if ( ! unavailable . IsEmpty )
203204 {
204- var reasons = string . Join ( " " , unavailable . Values . Distinct ( StringComparer . Ordinal ) . Take ( 2 ) ) ;
205- Mediator . Publish ( new NotificationMessage ( "Some files are temporarily unavailable" ,
206- $ "{ unavailable . Count } file(s) could not be downloaded. { reasons } ",
207- Configuration . Models . NotificationType . Warning , TimeSpan . FromSeconds ( 8 ) ) ) ;
205+ var actionableUnavailable = unavailable . Values
206+ . Where ( entry => entry . Reason != FileDownloadNegativeReason . Missing )
207+ . ToList ( ) ;
208+ if ( actionableUnavailable . Count > 0 )
209+ {
210+ var reasons = string . Join ( " " , actionableUnavailable . Select ( entry => entry . Message ) . Distinct ( StringComparer . Ordinal ) . Take ( 2 ) ) ;
211+ Mediator . Publish ( new NotificationMessage ( "Some files are temporarily unavailable" ,
212+ $ "{ actionableUnavailable . Count } file(s) could not be downloaded. { reasons } ",
213+ Configuration . Models . NotificationType . Warning , TimeSpan . FromSeconds ( 8 ) ) ) ;
214+ }
208215 }
209216
210217 LogDownloadEnd ( Logger , gameObjectHandler . Name ) ;
0 commit comments