@@ -21,10 +21,11 @@ public class ForgejoVersionCache : SafeDictionary<string, VersionCacheEntry>, IV
2121 public string ProjectPath => _cachedProject ! . full_name ! ;
2222
2323 private string ? _latestTag ;
24+ private bool _deriveLatestManually ;
2425
25- private PinnedVersions _pinnedVersions = null ! ;
26+ private PinnedVersions _pinnedVersions = null ! ; //late-init, see Init method
2627
27- PinnedVersions IVersionCache . PinnedVersions => _pinnedVersions ; //late-init, see Init method
28+ PinnedVersions IVersionCache . PinnedVersions => _pinnedVersions ;
2829
2930 // ReSharper disable once ReplaceWithFieldKeyword
3031
@@ -69,46 +70,49 @@ public ForgejoVersionCache(IConfiguration config, ForgejoService forgejoService,
6970
7071 public string ReleaseUrlFormat => $ "{ _forgejoEndpoint . TrimEnd ( '/' ) } /{ ProjectPath } /releases/tag/{{0}}";
7172
72- public void Init ( string projectId , PinnedVersions pinnedVersions ) => Executor . ExecuteBackgroundAsync ( async ( ) =>
73- {
74- try
75- {
76- _cachedProject ??= await _fj . Client . Repository . GetAsync ( projectId . Split ( '/' ) [ 0 ] , projectId . Split ( '/' ) [ 1 ] ) ;
77- }
78- catch ( ForgejoClientException e )
73+ public void Init ( string projectId , bool deriveLatestVersionManually , PinnedVersions pinnedVersions ) =>
74+ Executor . ExecuteBackgroundAsync ( async ( ) =>
7975 {
80- _logger . LogError (
81- "Encountered error when getting the project ({project}) for the version cache. Aborting. Error: {errorMessage}" ,
82- projectId , e . Message ) ;
83- return ;
84- }
85-
86- _logger . LogInformation ( "Initializing version cache for {project}" , ProjectName ) ;
76+ _deriveLatestManually = deriveLatestVersionManually ;
77+ try
78+ {
79+ _cachedProject ??=
80+ await _fj . Client . Repository . GetAsync ( projectId . Split ( '/' ) [ 0 ] , projectId . Split ( '/' ) [ 1 ] ) ;
81+ }
82+ catch ( ForgejoClientException e )
83+ {
84+ _logger . LogError (
85+ "Encountered error when getting the project ({project}) for the version cache. Aborting. Error: {errorMessage}" ,
86+ projectId , e . Message ) ;
87+ return ;
88+ }
8789
88- _pinnedVersions = pinnedVersions ;
90+ _logger . LogInformation ( "Initializing version cache for {project}" , ProjectName ) ;
8991
90- await RefreshAsync ( ) ;
92+ _pinnedVersions = pinnedVersions ;
9193
92- if ( _refreshTimer == null )
93- {
94- string howToRefresh = AdminEndpointMetadata . Enabled
95- ? $ "using the { Constants . FullRouteName_Api_Admin_RefreshCache } endpoint or restarting the server."
96- : "restarting the server. Set an admin access token in appsettings.json to enable an endpoint to do this." ;
94+ await RefreshAsync ( ) ;
9795
98- _logger . LogInformation (
99- "Periodic version cache refreshing is disabled for {project}. It can be refreshed by {means}" ,
100- ProjectName , howToRefresh ) ;
101- return ;
102- }
96+ if ( _refreshTimer == null )
97+ {
98+ string howToRefresh = AdminEndpointMetadata . Enabled
99+ ? $ "using the { Constants . FullRouteName_Api_Admin_RefreshCache } endpoint or restarting the server."
100+ : "restarting the server. Set an admin access token in appsettings.json to enable an endpoint to do this." ;
101+
102+ _logger . LogInformation (
103+ "Periodic version cache refreshing is disabled for {project}. It can be refreshed by {means}" ,
104+ ProjectName , howToRefresh ) ;
105+ return ;
106+ }
103107
104- _logger . LogInformation ( "Refreshing version cache for {project} every {timePeriod} minutes." ,
105- ProjectName , _refreshTimer . Period . TotalMinutes ) ;
108+ _logger . LogInformation ( "Refreshing version cache for {project} every {timePeriod} minutes." ,
109+ ProjectName , _refreshTimer . Period . TotalMinutes ) ;
106110
107- while ( await _refreshTimer . WaitForNextTickAsync ( ) )
108- {
109- await RefreshAsync ( ) ;
110- }
111- } ) ;
111+ while ( await _refreshTimer . WaitForNextTickAsync ( ) )
112+ {
113+ await RefreshAsync ( ) ;
114+ }
115+ } ) ;
112116
113117 public Task < IDisposable > TakeLockAsync ( ) => _semaphore . TakeAsync ( ) ;
114118
@@ -138,23 +142,28 @@ public async Task RefreshAsync()
138142 {
139143 _logger . LogInformation ( "Reloading version cache for {project}" , ProjectName ) ;
140144
141- try
142- {
143- _latestTag = await _fj . Client . Repository . GetReleaseLatestAsync (
144- ProjectPath . Split ( '/' ) [ 0 ] , ProjectPath . Split ( '/' ) [ 1 ]
145- ) . Then ( r => r . tag_name ) ;
146- }
147- catch ( Exception e )
145+ if ( ! _deriveLatestManually )
148146 {
149- _logger . LogWarning ( "Errored trying to get latest release for {project}; aborting. Message: {message}" ,
150- ProjectName , e . Message ) ;
151- return ;
152- }
147+ _logger . LogInformation ( "Requesting latest version for {project}." , ProjectPath ) ;
148+ try
149+ {
150+ _latestTag = await _fj . Client . Repository . GetReleaseLatestAsync (
151+ ProjectPath . Split ( '/' ) [ 0 ] , ProjectPath . Split ( '/' ) [ 1 ]
152+ ) . Then ( r => r . tag_name ) ;
153+ _logger . LogInformation ( "Latest received: {latestTag}." , _latestTag ) ;
154+ }
155+ catch ( Exception e )
156+ {
157+ _logger . LogWarning ( "Errored trying to get latest release for {project}; aborting. Message: {message}" ,
158+ ProjectName , e . Message ) ;
159+ return ;
160+ }
153161
154- if ( _latestTag is null )
155- {
156- _logger . LogWarning ( "Latest version for {project} was a 404, aborting." , ProjectName ) ;
157- return ;
162+ if ( _latestTag is null )
163+ {
164+ _logger . LogWarning ( "Latest version for {project} was a 404, aborting." , ProjectName ) ;
165+ return ;
166+ }
158167 }
159168
160169 var sw = Stopwatch . StartNew ( ) ;
@@ -164,6 +173,43 @@ public async Task RefreshAsync()
164173 ProjectPath . Split ( '/' ) [ 1 ]
165174 ) ;
166175
176+ if ( _deriveLatestManually )
177+ {
178+ _logger . LogInformation ( "Deriving latest version for {project}." , ProjectPath ) ;
179+ try
180+ {
181+ Dictionary < Version , string > versionMapping = new ( ) ;
182+ foreach ( var ver in releases . Select ( x => x . TagName ) . Where ( x => x != null ) )
183+ {
184+ if ( Version . TryParse ( ver , out Version ? result ) )
185+ {
186+ versionMapping [ result ] = ver ;
187+ }
188+ else
189+ {
190+ _logger . LogWarning (
191+ "'{version}' is not parseable as a .NET version. It will not be included in figuring out what release is latest." ,
192+ ver ) ;
193+ }
194+ }
195+
196+ _latestTag = versionMapping . OrderByDescending ( x => x . Key ) . FirstOrDefault ( ) . Value ;
197+ _logger . LogInformation ( "Latest found: {latestTag}." , _latestTag ) ;
198+ }
199+ catch ( Exception e )
200+ {
201+ _logger . LogWarning ( "Errored trying to get latest release for {project}; aborting. Message: {message}" ,
202+ ProjectName , e . Message ) ;
203+ return ;
204+ }
205+
206+ if ( _latestTag is null )
207+ {
208+ _logger . LogWarning ( "Latest version for {project} was a 404, aborting." , ProjectName ) ;
209+ return ;
210+ }
211+ }
212+
167213 var tempCacheEntries = releases . Select ( release =>
168214 new VersionCacheEntry
169215 {
@@ -237,45 +283,53 @@ public static void InitializeVersionCaches(WebApplication app)
237283 var versionCacheSection = app . Configuration . GetSection ( "Forgejo" )
238284 . GetRequiredSection ( "VersionCacheSources" ) ;
239285
240- var stableSource = versionCacheSection . GetValue < string > ( "Stable" ) ;
286+ var stableSource = versionCacheSection . GetSection ( "Stable" ) ;
241287
242- if ( stableSource is null )
288+ if ( ! stableSource . Exists ( ) )
243289 throw new Exception (
244- "Cannot start the server without a Forgejo repository in Forgejo:VersionCacheSources:Stable" ) ;
290+ "Cannot start the server without a Forgejo repository in Forgejo:VersionCacheSources:Stable:Project " ) ;
245291
246292 var vpSection = app . Configuration . GetSection ( "VersionPinning" ) ;
247293
248294 var pvLogger = app . Services . Get < ILoggerFactory > ( ) . CreateLogger < PinnedVersions > ( ) ;
249295
250- var stableCache = app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "stableCache" ) ;
251- stableCache . Init ( stableSource ,
252- new PinnedVersions ( pvLogger , vpSection . GetSection ( "Stable" ) ) ) ;
296+ app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "stableCache" ) . Init (
297+ stableSource . GetValue < string > ( "Project" ) ! ,
298+ stableSource . GetValue < bool > ( "DeriveLatestVersionManually" ) ,
299+ new PinnedVersions ( pvLogger , vpSection . GetSection ( "Stable" ) )
300+ ) ;
253301
254- var canarySource = versionCacheSection . GetValue < string > ( "Canary" ) ;
302+ var canarySource = versionCacheSection . GetSection ( "Canary" ) ;
255303
256- if ( canarySource != null )
304+ if ( canarySource . Exists ( ) )
257305 {
258- var canaryCache = app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "canaryCache" ) ;
259- canaryCache . Init ( canarySource ,
260- new PinnedVersions ( pvLogger , vpSection . GetSection ( "Canary" ) ) ) ;
306+ app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "canaryCache" ) . Init (
307+ canarySource . GetValue < string > ( "Project" ) ! ,
308+ canarySource . GetValue < bool > ( "DeriveLatestVersionManually" ) ,
309+ new PinnedVersions ( pvLogger , vpSection . GetSection ( "Canary" ) )
310+ ) ;
261311 }
262312
263- var custom1Source = versionCacheSection . GetValue < string > ( "Custom1" ) ;
313+ var custom1Source = versionCacheSection . GetSection ( "Custom1" ) ;
264314
265- if ( custom1Source != null )
315+ if ( custom1Source . Exists ( ) )
266316 {
267- var canaryCache = app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "custom1Cache" ) ;
268- canaryCache . Init ( custom1Source ,
269- new PinnedVersions ( pvLogger , vpSection . GetSection ( "Custom1" ) ) ) ;
317+ app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "custom1Cache" ) . Init (
318+ custom1Source . GetValue < string > ( "Project" ) ! ,
319+ custom1Source . GetValue < bool > ( "DeriveLatestVersionManually" ) ,
320+ new PinnedVersions ( pvLogger , vpSection . GetSection ( "Custom1" ) )
321+ ) ;
270322 }
271323
272- var kenjiNxSource = versionCacheSection . GetValue < string > ( "KenjiNX" ) ;
324+ var kenjiNxSource = versionCacheSection . GetSection ( "KenjiNX" ) ;
273325
274- if ( kenjiNxSource != null )
326+ if ( kenjiNxSource . Exists ( ) )
275327 {
276- var kenjiCache = app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "kenjinxCache" ) ;
277- kenjiCache . Init ( kenjiNxSource ,
278- new PinnedVersions ( pvLogger , vpSection . GetSection ( "KenjiNX" ) ) ) ;
328+ app . Services . GetRequiredKeyedService < ForgejoVersionCache > ( "kenjinxCache" ) . Init (
329+ kenjiNxSource . GetValue < string > ( "Project" ) ! ,
330+ kenjiNxSource . GetValue < bool > ( "DeriveLatestVersionManually" ) ,
331+ new PinnedVersions ( pvLogger , vpSection . GetSection ( "KenjiNX" ) )
332+ ) ;
279333 }
280334 }
281335}
0 commit comments