66using Microsoft . Extensions . Hosting ;
77using Microsoft . Extensions . Options ;
88using StardewModdingAPI . Toolkit ;
9+ using StardewModdingAPI . Toolkit . Framework . Clients . CurseForgeExport ;
10+ using StardewModdingAPI . Toolkit . Framework . Clients . CurseForgeExport . ResponseModels ;
911using StardewModdingAPI . Toolkit . Framework . Clients . NexusExport ;
1012using StardewModdingAPI . Toolkit . Framework . Clients . NexusExport . ResponseModels ;
1113using StardewModdingAPI . Toolkit . Framework . Clients . Wiki ;
14+ using StardewModdingAPI . Web . Framework . Caching . CurseForgeExport ;
1215using StardewModdingAPI . Web . Framework . Caching . Mods ;
1316using StardewModdingAPI . Web . Framework . Caching . NexusExport ;
1417using StardewModdingAPI . Web . Framework . Caching . Wiki ;
18+ using StardewModdingAPI . Web . Framework . Clients . CurseForge ;
1519using StardewModdingAPI . Web . Framework . Clients . Nexus ;
1620using StardewModdingAPI . Web . Framework . ConfigModels ;
1721
@@ -33,6 +37,12 @@ internal class BackgroundService : IHostedService, IDisposable
3337 /// <summary>The cache in which to store mod data.</summary>
3438 private static IModCacheRepository ? ModCache ;
3539
40+ /// <summary>The HTTP client for fetching the mod export from the CurseForge export API.</summary>
41+ private static ICurseForgeExportApiClient ? CurseForgeExportApiClient ;
42+
43+ /// <summary>The HTTP client for fetching the mod export from the CurseForge export API.</summary>
44+ private static ICurseForgeExportCacheRepository ? CurseForgeExportCache ;
45+
3646 /// <summary>The cache in which to store mod data from the Nexus export API.</summary>
3747 private static INexusExportCacheRepository ? NexusExportCache ;
3848
@@ -43,11 +53,20 @@ internal class BackgroundService : IHostedService, IDisposable
4353 private static IOptions < ModUpdateCheckConfig > ? UpdateCheckConfig ;
4454
4555 /// <summary>Whether the service has been started.</summary>
46- [ MemberNotNullWhen ( true , nameof ( BackgroundService . JobServer ) , nameof ( BackgroundService . ModCache ) , nameof ( NexusExportApiClient ) , nameof ( NexusExportCache ) , nameof ( BackgroundService . UpdateCheckConfig ) , nameof ( BackgroundService . WikiCache ) ) ]
56+ [ MemberNotNullWhen ( true ,
57+ nameof ( BackgroundService . JobServer ) ,
58+ nameof ( BackgroundService . ModCache ) ,
59+ nameof ( BackgroundService . CurseForgeExportApiClient ) ,
60+ nameof ( BackgroundService . CurseForgeExportCache ) ,
61+ nameof ( BackgroundService . NexusExportApiClient ) ,
62+ nameof ( BackgroundService . NexusExportCache ) ,
63+ nameof ( BackgroundService . UpdateCheckConfig ) ,
64+ nameof ( BackgroundService . WikiCache )
65+ ) ]
4766 private static bool IsStarted { get ; set ; }
4867
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 ;
68+ /// <summary>The number of minutes a site export should be considered valid based on its last-updated date before it's ignored.</summary>
69+ private static int ExportStaleAge => ( BackgroundService . UpdateCheckConfig ? . Value . SuccessCacheMinutes ?? 0 ) + 10 ;
5170
5271
5372 /*********
@@ -59,20 +78,24 @@ internal class BackgroundService : IHostedService, IDisposable
5978 /// <summary>Construct an instance.</summary>
6079 /// <param name="wikiCache">The cache in which to store wiki metadata.</param>
6180 /// <param name="modCache">The cache in which to store mod data.</param>
81+ /// <param name="curseForgeExportCache">The cache in which to store mod data from the CurseForge export API.</param>
82+ /// <param name="curseForgeExportApiClient">The HTTP client for fetching the mod export from the CurseForge export API.</param>
6283 /// <param name="nexusExportCache">The cache in which to store mod data from the Nexus export API.</param>
6384 /// <param name="nexusExportApiClient">The HTTP client for fetching the mod export from the Nexus Mods export API.</param>
6485 /// <param name="hangfireStorage">The Hangfire storage implementation.</param>
6586 /// <param name="updateCheckConfig">The config settings for mod update checks.</param>
6687 [ SuppressMessage ( "ReSharper" , "UnusedParameter.Local" , Justification = "The Hangfire reference forces it to initialize first, since it's needed by the background service." ) ]
67- public BackgroundService ( IWikiCacheRepository wikiCache , IModCacheRepository modCache , INexusExportCacheRepository nexusExportCache , INexusExportApiClient nexusExportApiClient , JobStorage hangfireStorage , IOptions < ModUpdateCheckConfig > updateCheckConfig )
88+ public BackgroundService ( IWikiCacheRepository wikiCache , IModCacheRepository modCache , ICurseForgeExportCacheRepository curseForgeExportCache , ICurseForgeExportApiClient curseForgeExportApiClient , INexusExportCacheRepository nexusExportCache , INexusExportApiClient nexusExportApiClient , JobStorage hangfireStorage , IOptions < ModUpdateCheckConfig > updateCheckConfig )
6889 {
6990 BackgroundService . WikiCache = wikiCache ;
7091 BackgroundService . ModCache = modCache ;
92+ BackgroundService . CurseForgeExportApiClient = curseForgeExportApiClient ;
93+ BackgroundService . CurseForgeExportCache = curseForgeExportCache ;
7194 BackgroundService . NexusExportCache = nexusExportCache ;
7295 BackgroundService . NexusExportApiClient = nexusExportApiClient ;
7396 BackgroundService . UpdateCheckConfig = updateCheckConfig ;
7497
75- _ = hangfireStorage ; // this parameter is only received so it's initialized before the background service
98+ _ = hangfireStorage ; // parameter is only received to initialize it before the background service
7699 }
77100
78101 /// <summary>Start the service.</summary>
@@ -81,16 +104,21 @@ public Task StartAsync(CancellationToken cancellationToken)
81104 {
82105 this . TryInit ( ) ;
83106
107+ bool enableCurseForgeExport = BackgroundService . CurseForgeExportApiClient is not DisabledCurseForgeExportApiClient ;
84108 bool enableNexusExport = BackgroundService . NexusExportApiClient is not DisabledNexusExportApiClient ;
85109
86110 // set startup tasks
87111 BackgroundJob . Enqueue ( ( ) => BackgroundService . UpdateWikiAsync ( ) ) ;
112+ if ( enableCurseForgeExport )
113+ BackgroundJob . Enqueue ( ( ) => BackgroundService . UpdateCurseForgeExportAsync ( ) ) ;
88114 if ( enableNexusExport )
89115 BackgroundJob . Enqueue ( ( ) => BackgroundService . UpdateNexusExportAsync ( ) ) ;
90116 BackgroundJob . Enqueue ( ( ) => BackgroundService . RemoveStaleModsAsync ( ) ) ;
91117
92118 // set recurring tasks
93119 RecurringJob . AddOrUpdate ( "update wiki data" , ( ) => BackgroundService . UpdateWikiAsync ( ) , "*/10 * * * *" ) ; // every 10 minutes
120+ if ( enableCurseForgeExport )
121+ RecurringJob . AddOrUpdate ( "update CurseForge export" , ( ) => BackgroundService . UpdateCurseForgeExportAsync ( ) , "*/10 * * * *" ) ;
94122 if ( enableNexusExport )
95123 RecurringJob . AddOrUpdate ( "update Nexus export" , ( ) => BackgroundService . UpdateNexusExportAsync ( ) , "*/10 * * * *" ) ;
96124 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)
@@ -132,6 +160,21 @@ public static async Task UpdateWikiAsync()
132160 BackgroundService . WikiCache . SaveWikiData ( wikiCompatList . StableVersion , wikiCompatList . BetaVersion , wikiCompatList . Mods ) ;
133161 }
134162
163+ /// <summary>Update the cached CurseForge mod dump.</summary>
164+ [ AutomaticRetry ( Attempts = 3 , DelaysInSeconds = new [ ] { 30 , 60 , 120 } ) ]
165+ public static async Task UpdateCurseForgeExportAsync ( )
166+ {
167+ if ( ! BackgroundService . IsStarted )
168+ throw new InvalidOperationException ( $ "Must call { nameof ( BackgroundService . StartAsync ) } before scheduling tasks.") ;
169+
170+ CurseForgeFullExport data = await BackgroundService . CurseForgeExportApiClient . FetchExportAsync ( ) ;
171+
172+ var cache = BackgroundService . CurseForgeExportCache ;
173+ cache . SetData ( data ) ;
174+ if ( cache . IsStale ( BackgroundService . ExportStaleAge ) )
175+ cache . SetData ( null ) ; // if the export is too old, fetch fresh mod data from the site/API instead
176+ }
177+
135178 /// <summary>Update the cached Nexus mod dump.</summary>
136179 [ AutomaticRetry ( Attempts = 3 , DelaysInSeconds = new [ ] { 30 , 60 , 120 } ) ]
137180 public static async Task UpdateNexusExportAsync ( )
@@ -143,7 +186,7 @@ public static async Task UpdateNexusExportAsync()
143186
144187 var cache = BackgroundService . NexusExportCache ;
145188 cache . SetData ( data ) ;
146- if ( cache . IsStale ( BackgroundService . NexusExportStaleAge ) )
189+ if ( cache . IsStale ( BackgroundService . ExportStaleAge ) )
147190 cache . SetData ( null ) ; // if the export is too old, fetch fresh mod data from the site/API instead
148191 }
149192
@@ -157,7 +200,7 @@ public static Task RemoveStaleModsAsync()
157200 BackgroundService . ModCache . RemoveStaleMods ( TimeSpan . FromHours ( 48 ) ) ;
158201
159202 // remove stale export cache
160- if ( BackgroundService . NexusExportCache . IsStale ( BackgroundService . NexusExportStaleAge ) )
203+ if ( BackgroundService . NexusExportCache . IsStale ( BackgroundService . ExportStaleAge ) )
161204 BackgroundService . NexusExportCache . SetData ( null ) ;
162205
163206 return Task . CompletedTask ;
0 commit comments