Skip to content

Commit bdae18b

Browse files
committed
initial support
1 parent 43d2f90 commit bdae18b

36 files changed

Lines changed: 4645 additions & 223 deletions

db/manifests.json

Lines changed: 3717 additions & 0 deletions
Large diffs are not rendered by default.

src/Addons/Addons/BaseAddon.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ public abstract class BaseAddon
101101
/// </summary>
102102
public bool IsFavorite { get; set; }
103103

104+
/// <summary>
105+
/// Is update for metadata available.
106+
/// </summary>
107+
public bool IsMetadataUpdateAvailable { get; set; }
108+
104109
/// <summary>
105110
/// List of built-in executables.
106111
/// </summary>

src/Addons/DI/ProvidersBindings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Addons.Providers;
2+
using Common.Client.Providers;
23
using Microsoft.Extensions.DependencyInjection;
34

45
namespace Addons.DI;
@@ -10,5 +11,6 @@ public static void Load(ServiceCollection container)
1011
_ = container.AddSingleton<InstalledAddonsProviderFactory>();
1112
_ = container.AddSingleton<DownloadableAddonsProviderFactory>();
1213
_ = container.AddSingleton<OriginalCampaignsProvider>();
14+
_ = container.AddSingleton<MetadataProvider>();
1315
}
1416
}

src/Addons/Providers/InstalledAddonsProvider.cs

Lines changed: 112 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Common.Client.Cache;
1111
using Common.Client.Helpers;
1212
using Common.Client.Interfaces;
13+
using Common.Client.Providers;
1314
using Games.Games;
1415
using Microsoft.Extensions.DependencyInjection;
1516
using 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

11821252
internal struct AddonCarcass

src/Addons/Providers/InstalledAddonsProviderFactory.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Common.All.Enums;
22
using Common.Client.Cache;
33
using Common.Client.Interfaces;
4+
using Common.Client.Providers;
45
using Games.Games;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Logging;
@@ -13,18 +14,21 @@ public sealed class InstalledAddonsProviderFactory
1314
private readonly IConfigProvider _config;
1415
private readonly ICacheAdder<Stream> _bitmapsCache;
1516
private readonly OriginalCampaignsProvider _originalCampaignsProvider;
17+
private readonly MetadataProvider _metadataProvider;
1618
private readonly ILogger _logger;
1719

1820
public InstalledAddonsProviderFactory(
1921
IConfigProvider config,
2022
[FromKeyedServices("Bitmaps")] ICacheAdder<Stream> bitmapsCache,
2123
OriginalCampaignsProvider originalCampaignsProvider,
24+
MetadataProvider metadataProvider,
2225
ILogger logger
2326
)
2427
{
2528
_config = config;
2629
_bitmapsCache = bitmapsCache;
2730
_originalCampaignsProvider = originalCampaignsProvider;
31+
_metadataProvider = metadataProvider;
2832
_logger = logger;
2933
}
3034

@@ -40,7 +44,14 @@ public InstalledAddonsProvider Get(BaseGame game)
4044
}
4145

4246
#pragma warning disable CS0618 // Type or member is obsolete
43-
InstalledAddonsProvider newProvider = new(game, _config, _logger, _bitmapsCache, _originalCampaignsProvider);
47+
InstalledAddonsProvider newProvider = new(
48+
game,
49+
_config,
50+
_logger,
51+
_bitmapsCache,
52+
_originalCampaignsProvider,
53+
_metadataProvider
54+
);
4455
#pragma warning restore CS0618 // Type or member is obsolete
4556
_list.Add(game.GameEnum, newProvider);
4657

src/Addons/Providers/OriginalCampaignsProvider.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,9 @@ private Dictionary<AddonId, BaseAddon> GetRedneckCampaigns(BaseGame game)
536536

537537
if (rGame.IsBaseGameInstalled)
538538
{
539-
{var redneckId = nameof(GameEnum.Redneck).ToLower();
540-
AddonId version = new(redneckId, null);
539+
{
540+
var redneckId = nameof(GameEnum.Redneck).ToLower();
541+
AddonId version = new(redneckId, null);
541542

542543
campaigns.Add(version, new DukeCampaign()
543544
{

0 commit comments

Comments
 (0)