44using System . Threading . Tasks ;
55using Hangfire ;
66using Microsoft . Extensions . Hosting ;
7+ using Microsoft . Extensions . Options ;
78using StardewModdingAPI . Toolkit ;
9+ using StardewModdingAPI . Toolkit . Framework . Clients . NexusExport ;
10+ using StardewModdingAPI . Toolkit . Framework . Clients . NexusExport . ResponseModels ;
811using StardewModdingAPI . Toolkit . Framework . Clients . Wiki ;
912using StardewModdingAPI . Web . Framework . Caching . Mods ;
13+ using StardewModdingAPI . Web . Framework . Caching . NexusExport ;
1014using StardewModdingAPI . Web . Framework . Caching . Wiki ;
15+ using StardewModdingAPI . Web . Framework . Clients . Nexus ;
16+ using StardewModdingAPI . Web . Framework . ConfigModels ;
1117
1218namespace StardewModdingAPI . Web
1319{
@@ -27,10 +33,22 @@ internal class BackgroundService : IHostedService, IDisposable
2733 /// <summary>The cache in which to store mod data.</summary>
2834 private static IModCacheRepository ? ModCache ;
2935
36+ /// <summary>The cache in which to store mod data from the Nexus export API.</summary>
37+ private static INexusExportCacheRepository ? NexusExportCache ;
38+
39+ /// <summary>The HTTP client for fetching the mod export from the Nexus Mods export API.</summary>
40+ private static INexusExportApiClient ? NexusExportApiClient ;
41+
42+ /// <summary>The config settings for mod update checks.</summary>
43+ private static IOptions < ModUpdateCheckConfig > ? UpdateCheckConfig ;
44+
3045 /// <summary>Whether the service has been started.</summary>
31- [ MemberNotNullWhen ( true , nameof ( BackgroundService . JobServer ) , nameof ( BackgroundService . WikiCache ) , nameof ( BackgroundService . ModCache ) ) ]
46+ [ MemberNotNullWhen ( true , nameof ( BackgroundService . JobServer ) , nameof ( BackgroundService . ModCache ) , nameof ( NexusExportApiClient ) , nameof ( NexusExportCache ) , nameof ( BackgroundService . UpdateCheckConfig ) , nameof ( BackgroundService . WikiCache ) ) ]
3247 private static bool IsStarted { get ; set ; }
3348
49+ /// <summary>The number of minutes the Nexus export should be considered valid based on its last-updated date before it's ignored.</summary>
50+ private static int NexusExportStaleAge => ( BackgroundService . UpdateCheckConfig ? . Value . SuccessCacheMinutes ?? 0 ) + 10 ;
51+
3452
3553 /*********
3654 ** Public methods
@@ -41,12 +59,20 @@ internal class BackgroundService : IHostedService, IDisposable
4159 /// <summary>Construct an instance.</summary>
4260 /// <param name="wikiCache">The cache in which to store wiki metadata.</param>
4361 /// <param name="modCache">The cache in which to store mod data.</param>
62+ /// <param name="nexusExportCache">The cache in which to store mod data from the Nexus export API.</param>
63+ /// <param name="nexusExportApiClient">The HTTP client for fetching the mod export from the Nexus Mods export API.</param>
4464 /// <param name="hangfireStorage">The Hangfire storage implementation.</param>
65+ /// <param name="updateCheckConfig">The config settings for mod update checks.</param>
4566 [ SuppressMessage ( "ReSharper" , "UnusedParameter.Local" , Justification = "The Hangfire reference forces it to initialize first, since it's needed by the background service." ) ]
46- public BackgroundService ( IWikiCacheRepository wikiCache , IModCacheRepository modCache , JobStorage hangfireStorage )
67+ public BackgroundService ( IWikiCacheRepository wikiCache , IModCacheRepository modCache , INexusExportCacheRepository nexusExportCache , INexusExportApiClient nexusExportApiClient , JobStorage hangfireStorage , IOptions < ModUpdateCheckConfig > updateCheckConfig )
4768 {
4869 BackgroundService . WikiCache = wikiCache ;
4970 BackgroundService . ModCache = modCache ;
71+ BackgroundService . NexusExportCache = nexusExportCache ;
72+ BackgroundService . NexusExportApiClient = nexusExportApiClient ;
73+ BackgroundService . UpdateCheckConfig = updateCheckConfig ;
74+
75+ _ = hangfireStorage ; // this parameter is only received so it's initialized before the background service
5076 }
5177
5278 /// <summary>Start the service.</summary>
@@ -55,13 +81,19 @@ public Task StartAsync(CancellationToken cancellationToken)
5581 {
5682 this . TryInit ( ) ;
5783
84+ bool enableNexusExport = BackgroundService . NexusExportApiClient is not DisabledNexusExportApiClient ;
85+
5886 // set startup tasks
5987 BackgroundJob . Enqueue ( ( ) => BackgroundService . UpdateWikiAsync ( ) ) ;
88+ if ( enableNexusExport )
89+ BackgroundJob . Enqueue ( ( ) => BackgroundService . UpdateNexusExportAsync ( ) ) ;
6090 BackgroundJob . Enqueue ( ( ) => BackgroundService . RemoveStaleModsAsync ( ) ) ;
6191
6292 // set recurring tasks
63- RecurringJob . AddOrUpdate ( ( ) => BackgroundService . UpdateWikiAsync ( ) , "*/10 * * * *" ) ; // every 10 minutes
64- RecurringJob . AddOrUpdate ( ( ) => BackgroundService . RemoveStaleModsAsync ( ) , "0 * * * *" ) ; // hourly
93+ RecurringJob . AddOrUpdate ( "update wiki data" , ( ) => BackgroundService . UpdateWikiAsync ( ) , "*/10 * * * *" ) ; // every 10 minutes
94+ if ( enableNexusExport )
95+ RecurringJob . AddOrUpdate ( "update Nexus export" , ( ) => BackgroundService . UpdateNexusExportAsync ( ) , "*/10 * * * *" ) ;
96+ RecurringJob . AddOrUpdate ( "remove stale mods" , ( ) => BackgroundService . RemoveStaleModsAsync ( ) , "2/10 * * * *" ) ; // offset by 2 minutes so it runs after updates (e.g. 00:02, 00:12, etc)
6597
6698 BackgroundService . IsStarted = true ;
6799
@@ -100,13 +132,34 @@ public static async Task UpdateWikiAsync()
100132 BackgroundService . WikiCache . SaveWikiData ( wikiCompatList . StableVersion , wikiCompatList . BetaVersion , wikiCompatList . Mods ) ;
101133 }
102134
135+ /// <summary>Update the cached Nexus mod dump.</summary>
136+ [ AutomaticRetry ( Attempts = 3 , DelaysInSeconds = new [ ] { 30 , 60 , 120 } ) ]
137+ public static async Task UpdateNexusExportAsync ( )
138+ {
139+ if ( ! BackgroundService . IsStarted )
140+ throw new InvalidOperationException ( $ "Must call { nameof ( BackgroundService . StartAsync ) } before scheduling tasks.") ;
141+
142+ NexusFullExport data = await BackgroundService . NexusExportApiClient . FetchExportAsync ( ) ;
143+
144+ var cache = BackgroundService . NexusExportCache ;
145+ cache . SetData ( data ) ;
146+ if ( cache . IsStale ( BackgroundService . NexusExportStaleAge ) )
147+ cache . SetData ( null ) ; // if the export is too old, fetch fresh mod data from the site/API instead
148+ }
149+
103150 /// <summary>Remove mods which haven't been requested in over 48 hours.</summary>
104151 public static Task RemoveStaleModsAsync ( )
105152 {
106153 if ( ! BackgroundService . IsStarted )
107154 throw new InvalidOperationException ( $ "Must call { nameof ( BackgroundService . StartAsync ) } before scheduling tasks.") ;
108155
156+ // remove mods in mod cache
109157 BackgroundService . ModCache . RemoveStaleMods ( TimeSpan . FromHours ( 48 ) ) ;
158+
159+ // remove stale export cache
160+ if ( BackgroundService . NexusExportCache . IsStale ( BackgroundService . NexusExportStaleAge ) )
161+ BackgroundService . NexusExportCache . SetData ( null ) ;
162+
110163 return Task . CompletedTask ;
111164 }
112165
0 commit comments