1010using Common . Client . Cache ;
1111using Common . Client . Helpers ;
1212using Common . Client . Interfaces ;
13+ using Common . Client . Providers ;
1314using Games . Games ;
1415using Microsoft . Extensions . DependencyInjection ;
1516using Microsoft . Extensions . Logging ;
@@ -20,22 +21,21 @@ namespace Addons.Providers;
2021/// <summary>
2122/// Class that provides lists of installed mods
2223/// </summary>
23- public sealed class InstalledAddonsProvider
24+ public sealed class InstalledAddonsProvider : IDisposable
2425{
2526 private readonly BaseGame _game ;
2627 private readonly IConfigProvider _config ;
2728 private readonly ILogger _logger ;
2829 private readonly ICacheAdder < Stream > _bitmapsCache ;
2930 private readonly OriginalCampaignsProvider _originalCampaignsProvider ;
31+ private readonly MetadataProvider _metadataProvider ;
3032
3133 private readonly Dictionary < AddonId , BaseAddon > _campaignsCache = [ ] ;
3234 private readonly Dictionary < AddonId , BaseAddon > _mapsCache = [ ] ;
3335 private readonly Dictionary < AddonId , BaseAddon > _modsCache = [ ] ;
3436
3537 private readonly SemaphoreSlim _semaphore = new ( 1 ) ;
3638
37- private volatile bool _isCacheUpdating ;
38-
3939 public event AddonChanged ? AddonsChangedEvent ;
4040
4141 [ Obsolete ( $ "Don't create directly. Use { nameof ( InstalledAddonsProviderFactory ) } .") ]
@@ -44,14 +44,18 @@ public InstalledAddonsProvider(
4444 IConfigProvider config ,
4545 ILogger logger ,
4646 [ FromKeyedServices ( "Bitmaps" ) ] ICacheAdder < Stream > bitmapsCache ,
47- OriginalCampaignsProvider originalCampaignsProvider
47+ OriginalCampaignsProvider originalCampaignsProvider ,
48+ MetadataProvider metadataProvider
4849 )
4950 {
5051 _game = game ;
5152 _config = config ;
5253 _logger = logger ;
5354 _bitmapsCache = bitmapsCache ;
5455 _originalCampaignsProvider = originalCampaignsProvider ;
56+ _metadataProvider = metadataProvider ;
57+
58+ _metadataProvider . MetadataUpdatedEvent += OnMetadataUpdatedAsync ;
5559 }
5660
5761
@@ -112,7 +116,6 @@ public async Task<bool> CopyAddonIntoFolder(string pathToFile)
112116 return true ;
113117 }
114118
115-
116119 /// <summary>
117120 /// Create cache of installed addons.
118121 /// </summary>
@@ -122,8 +125,6 @@ public async Task CreateCache(bool createNew, AddonTypeEnum addonType)
122125 {
123126 await _semaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
124127
125- _isCacheUpdating = true ;
126-
127128 var cache = addonType switch
128129 {
129130 AddonTypeEnum . TC => _campaignsCache ,
@@ -238,7 +239,6 @@ public async Task CreateCache(bool createNew, AddonTypeEnum addonType)
238239 }
239240 finally
240241 {
241- _isCacheUpdating = false ;
242242 _ = _semaphore . Release ( ) ;
243243 ArgumentNullException . ThrowIfNull ( _campaignsCache ) ;
244244 ArgumentNullException . ThrowIfNull ( _mapsCache ) ;
@@ -431,66 +431,88 @@ public IReadOnlyDictionary<AddonId, BaseAddon> GetInstalledAddonsByType(AddonTyp
431431 } ;
432432 }
433433
434+
434435 private IReadOnlyDictionary < AddonId , BaseAddon > GetInstalledCampaigns ( )
435436 {
436437 var campaigns = _originalCampaignsProvider . GetOriginalCampaigns ( _game ) ;
437438
438- if ( _isCacheUpdating )
439+ if ( ! _semaphore . Wait ( 1 ) )
439440 {
440441 return campaigns ;
441442 }
442443
443- ArgumentNullException . ThrowIfNull ( _campaignsCache ) ;
444-
445- if ( _campaignsCache . Count == 0 )
444+ try
446445 {
447- return campaigns ;
448- }
446+ ArgumentNullException . ThrowIfNull ( _campaignsCache ) ;
449447
450- if ( _game . GameEnum is GameEnum . Wang )
451- {
452- //hack to make SW addons appear at the top of the list
453- foreach ( var customCamp in _campaignsCache
454- . OrderByDescending ( static x => x . Key . Id . Equals ( nameof ( WangAddonEnum . TwinDragon ) , StringComparison . OrdinalIgnoreCase ) )
455- . ThenByDescending ( static x => x . Key . Id . Equals ( nameof ( WangAddonEnum . Wanton ) , StringComparison . OrdinalIgnoreCase ) )
456- . ThenBy ( static x => x . Value . Title ) )
448+ if ( _campaignsCache . Count == 0 )
457449 {
458- campaigns . Add ( customCamp . Key , customCamp . Value ) ;
450+ return campaigns ;
459451 }
460- }
461- else
462- {
463- foreach ( var customCamp in _campaignsCache . OrderBy ( static x => x . Value . Title ) )
452+
453+ if ( _game . GameEnum is GameEnum . Wang )
464454 {
465- campaigns . Add ( customCamp . Key , customCamp . Value ) ;
455+ //hack to make SW addons appear at the top of the list
456+ foreach ( var customCamp in _campaignsCache
457+ . OrderByDescending ( static x => x . Key . Id . Equals ( nameof ( WangAddonEnum . TwinDragon ) , StringComparison . OrdinalIgnoreCase ) )
458+ . ThenByDescending ( static x => x . Key . Id . Equals ( nameof ( WangAddonEnum . Wanton ) , StringComparison . OrdinalIgnoreCase ) )
459+ . ThenBy ( static x => x . Value . Title ) )
460+ {
461+ campaigns . Add ( customCamp . Key , customCamp . Value ) ;
462+ }
463+ }
464+ else
465+ {
466+ foreach ( var customCamp in _campaignsCache . OrderBy ( static x => x . Value . Title ) )
467+ {
468+ campaigns . Add ( customCamp . Key , customCamp . Value ) ;
469+ }
466470 }
467- }
468471
469- return campaigns ;
472+ return campaigns ;
473+ }
474+ finally
475+ {
476+ _semaphore . Release ( ) ;
477+ }
470478 }
471479
472480 private IReadOnlyDictionary < AddonId , BaseAddon > GetInstalledMaps ( )
473481 {
474- if ( _isCacheUpdating )
482+ if ( ! _semaphore . Wait ( 1 ) )
475483 {
476484 return new Dictionary < AddonId , BaseAddon > ( ) ;
477485 }
478486
479- ArgumentNullException . ThrowIfNull ( _mapsCache ) ;
487+ try
488+ {
489+ ArgumentNullException . ThrowIfNull ( _mapsCache ) ;
480490
481- return _mapsCache ;
491+ return _mapsCache ;
492+ }
493+ finally
494+ {
495+ _semaphore . Release ( ) ;
496+ }
482497 }
483498
484499 private IReadOnlyDictionary < AddonId , BaseAddon > GetInstalledMods ( )
485500 {
486- if ( _isCacheUpdating )
501+ if ( ! _semaphore . Wait ( 1 ) )
487502 {
488503 return new Dictionary < AddonId , BaseAddon > ( ) ;
489504 }
490505
491- ArgumentNullException . ThrowIfNull ( _modsCache ) ;
506+ try
507+ {
508+ ArgumentNullException . ThrowIfNull ( _modsCache ) ;
492509
493- return _modsCache ;
510+ return _modsCache ;
511+ }
512+ finally
513+ {
514+ _semaphore . Release ( ) ;
515+ }
494516 }
495517
496518 /// <summary>
@@ -672,7 +694,8 @@ newAddon.AddonId.Version is not null &&
672694 IsUnpacked = false ,
673695 Executables = null ,
674696 Options = null ,
675- IsFavorite = _config . FavoriteAddons . Contains ( id )
697+ IsFavorite = _config . FavoriteAddons . Contains ( id ) ,
698+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
676699 } ;
677700
678701 return [ addon ] ;
@@ -797,7 +820,8 @@ newAddon.AddonId.Version is not null &&
797820 IsUnpacked = carcass . IsUnpacked ,
798821 Executables = null ,
799822 Options = null ,
800- IsFavorite = _config . FavoriteAddons . Contains ( id )
823+ IsFavorite = _config . FavoriteAddons . Contains ( id ) ,
824+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
801825 } ;
802826
803827 addons . Add ( addon ) ;
@@ -835,7 +859,8 @@ or GameEnum.NAM
835859 IsUnpacked = carcass . IsUnpacked ,
836860 Executables = carcass . Executables ,
837861 Options = carcass . Options ,
838- IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) )
862+ IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) ) ,
863+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
839864 } ;
840865
841866 addons . Add ( addon ) ;
@@ -863,7 +888,8 @@ or GameEnum.NAM
863888 IsUnpacked = carcass . IsUnpacked ,
864889 Executables = carcass . Executables ,
865890 Options = carcass . Options ,
866- IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) )
891+ IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) ) ,
892+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
867893 } ;
868894
869895 addons . Add ( addon ) ;
@@ -894,7 +920,8 @@ or GameEnum.NAM
894920 IsUnpacked = carcass . IsUnpacked ,
895921 Executables = carcass . Executables ,
896922 Options = carcass . Options ,
897- IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) )
923+ IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) ) ,
924+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
898925 } ;
899926
900927 addons . Add ( addon ) ;
@@ -922,7 +949,8 @@ or GameEnum.NAM
922949 IsUnpacked = carcass . IsUnpacked ,
923950 Executables = carcass . Executables ,
924951 Options = carcass . Options ,
925- IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) )
952+ IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) ) ,
953+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
926954 } ;
927955
928956 addons . Add ( addon ) ;
@@ -950,7 +978,8 @@ or GameEnum.NAM
950978 IsUnpacked = carcass . IsUnpacked ,
951979 Executables = carcass . Executables ,
952980 Options = carcass . Options ,
953- IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) )
981+ IsFavorite = _config . FavoriteAddons . Contains ( new ( carcass . Id , carcass . Version ) ) ,
982+ IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( pathToFile ) ,
954983 } ;
955984
956985 addons . Add ( addon ) ;
@@ -1177,6 +1206,47 @@ private AddonCarcass GetCarcass(
11771206
11781207 return carcass ;
11791208 }
1209+
1210+
1211+ private async void OnMetadataUpdatedAsync ( object ? sender , string ? e )
1212+ {
1213+ IEnumerable < BaseAddon > allAddons = [ .. _campaignsCache . Values , .. _mapsCache . Values , .. _modsCache . Values ] ;
1214+
1215+ foreach ( var camp in allAddons )
1216+ {
1217+ if ( camp . PathToFile is null )
1218+ {
1219+ continue ;
1220+ }
1221+
1222+ if ( e is null )
1223+ {
1224+ camp . IsMetadataUpdateAvailable = _metadataProvider . IsMetadataUpdateAvailable ( camp . PathToFile ) ;
1225+ }
1226+ else if ( camp . PathToFile . Equals ( e )
1227+ && camp . IsMetadataUpdateAvailable
1228+ && ! _metadataProvider . IsMetadataUpdateAvailable ( camp . PathToFile ) )
1229+ {
1230+ _campaignsCache . Remove ( camp . AddonId ) ;
1231+ await AddAddonAsync ( e ) . ConfigureAwait ( false ) ;
1232+
1233+ AddonsChangedEvent ? . Invoke ( _game . GameEnum , camp . Type ) ;
1234+ }
1235+ }
1236+
1237+ if ( e is null )
1238+ {
1239+ AddonsChangedEvent ? . Invoke ( _game . GameEnum , AddonTypeEnum . TC ) ;
1240+ AddonsChangedEvent ? . Invoke ( _game . GameEnum , AddonTypeEnum . Map ) ;
1241+ AddonsChangedEvent ? . Invoke ( _game . GameEnum , AddonTypeEnum . Mod ) ;
1242+ }
1243+ }
1244+
1245+
1246+ public void Dispose ( )
1247+ {
1248+ _metadataProvider . MetadataUpdatedEvent -= OnMetadataUpdatedAsync ;
1249+ }
11801250}
11811251
11821252internal struct AddonCarcass
0 commit comments