11using System ;
22using System . Collections . Generic ;
33using System . Diagnostics ;
4+ using System . Net . Http ;
45using System . Net . Http . Json ;
6+ using System . Net . Http . Headers ;
57using System . Text . Json . Serialization ;
68using System . Threading ;
79using System . Threading . Tasks ;
@@ -19,6 +21,8 @@ public sealed partial class EngineManagerDynamic
1921
2022 private Dictionary < string , VersionInfo > ? _cachedRobustVersionInfo ;
2123 private TimeSpan _robustCacheValidUntil ;
24+ private EntityTagHeaderValue ? _cachedManifestEtag ;
25+ private DateTimeOffset ? _cachedManifestLastModified ;
2226
2327 /// <summary>
2428 /// Look up information about an engine version.
@@ -67,17 +71,42 @@ public sealed partial class EngineManagerDynamic
6771
6872 private async Task UpdateBuildManifest ( CancellationToken cancel )
6973 {
70- // TODO: If-Modified-Since and If-None-Match request conditions.
71-
7274 Log . Debug ( "Loading manifest from {manifestUrl}..." , ConfigConstants . RobustBuildsManifest ) ;
7375 using var timeoutCts = CancellationTokenSource . CreateLinkedTokenSource ( cancel ) ;
7476 timeoutCts . CancelAfter ( ManifestLoadTimeout ) ;
7577
7678 try
7779 {
80+ using var response = await ConfigConstants . RobustBuildsManifest . SendAsync (
81+ _http ,
82+ url =>
83+ {
84+ var req = new HttpRequestMessage ( HttpMethod . Get , url ) ;
85+ if ( _cachedManifestEtag != null )
86+ req . Headers . IfNoneMatch . Add ( _cachedManifestEtag ) ;
87+ if ( _cachedManifestLastModified != null )
88+ req . Headers . IfModifiedSince = _cachedManifestLastModified ;
89+ return req ;
90+ } ,
91+ timeoutCts . Token ) ;
92+
93+ if ( response . StatusCode == System . Net . HttpStatusCode . NotModified && _cachedRobustVersionInfo != null )
94+ {
95+ Log . Debug ( "Manifest not modified, using cached copy" ) ;
96+ _robustCacheValidUntil = _manifestStopwatch . Elapsed + GetManifestCacheDuration ( response ) ;
97+ return ;
98+ }
99+
100+ response . EnsureSuccessStatusCode ( ) ;
101+
102+ _cachedManifestEtag = response . Headers . ETag ;
103+ _cachedManifestLastModified = response . Content . Headers . LastModified ;
104+
78105 _cachedRobustVersionInfo =
79- await ConfigConstants . RobustBuildsManifest . GetFromJsonAsync < Dictionary < string , VersionInfo > > (
80- _http , timeoutCts . Token ) ;
106+ await response . Content . ReadFromJsonAsync < Dictionary < string , VersionInfo > > ( timeoutCts . Token )
107+ ?? throw new InvalidOperationException ( "Robust manifest response was empty." ) ;
108+
109+ _robustCacheValidUntil = _manifestStopwatch . Elapsed + GetManifestCacheDuration ( response ) ;
81110 }
82111 catch ( OperationCanceledException e ) when ( ! cancel . IsCancellationRequested && timeoutCts . IsCancellationRequested )
83112 {
@@ -86,8 +115,25 @@ await ConfigConstants.RobustBuildsManifest.GetFromJsonAsync<Dictionary<string, V
86115 "Check your proxy/tunnel stability or disable proxy for launcher downloads." ,
87116 e ) ;
88117 }
118+ }
119+
120+ private static TimeSpan GetManifestCacheDuration ( HttpResponseMessage ? response )
121+ {
122+ if ( response ? . Headers . CacheControl ? . NoStore == true || response ? . Headers . CacheControl ? . NoCache == true )
123+ return TimeSpan . Zero ;
124+
125+ var maxAge = response ? . Headers . CacheControl ? . MaxAge ;
126+ if ( maxAge . HasValue )
127+ return maxAge . Value ;
128+
129+ if ( response ? . Content . Headers . Expires is { } expires )
130+ {
131+ var delta = expires - DateTimeOffset . UtcNow ;
132+ if ( delta > TimeSpan . Zero )
133+ return delta ;
134+ }
89135
90- _robustCacheValidUntil = _manifestStopwatch . Elapsed + ConfigConstants . RobustManifestCacheTime ;
136+ return ConfigConstants . RobustManifestCacheTime ;
91137 }
92138
93139 private FoundVersionInfo ? FindVersionInfoInCached ( string version , bool followRedirects )
0 commit comments