Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,717 changes: 3,717 additions & 0 deletions db/manifests.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/Addons/Addons/BaseAddon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ public abstract class BaseAddon
/// </summary>
public bool IsFavorite { get; set; }

/// <summary>
/// Is update for metadata available.
/// </summary>
public bool IsMetadataUpdateAvailable { get; set; }

/// <summary>
/// List of built-in executables.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Addons/DI/ProvidersBindings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Addons.Providers;
using Common.Client.Providers;
using Microsoft.Extensions.DependencyInjection;

namespace Addons.DI;
Expand All @@ -10,5 +11,6 @@ public static void Load(ServiceCollection container)
_ = container.AddSingleton<InstalledAddonsProviderFactory>();
_ = container.AddSingleton<DownloadableAddonsProviderFactory>();
_ = container.AddSingleton<OriginalCampaignsProvider>();
_ = container.AddSingleton<MetadataProvider>();
}
}
5 changes: 3 additions & 2 deletions src/Addons/Providers/DownloadableAddonsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public sealed class DownloadableAddonsProvider

private static readonly SemaphoreSlim _semaphore = new(1);

public event AddonChanged? AddonDownloadedEvent;
public event AddonChanged? AddonsChangedEvent;

/// <summary>
/// Download progress
Expand Down Expand Up @@ -105,6 +105,7 @@ public async Task<bool> CreateCacheAsync(bool createNew)
finally
{
_ = _semaphore.Release();
AddonsChangedEvent?.Invoke(_game.GameEnum, null);
}
}

Expand Down Expand Up @@ -237,7 +238,7 @@ CancellationToken cancellationToken
}
}

AddonDownloadedEvent?.Invoke(_game.GameEnum, addon.AddonType);
AddonsChangedEvent?.Invoke(_game.GameEnum, addon.AddonType);

return true;
}
Expand Down
164 changes: 122 additions & 42 deletions src/Addons/Providers/InstalledAddonsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Common.Client.Cache;
using Common.Client.Helpers;
using Common.Client.Interfaces;
using Common.Client.Providers;
using Games.Games;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -20,22 +21,21 @@ namespace Addons.Providers;
/// <summary>
/// Class that provides lists of installed mods
/// </summary>
public sealed class InstalledAddonsProvider
public sealed class InstalledAddonsProvider : IDisposable
{
private readonly BaseGame _game;
private readonly IConfigProvider _config;
private readonly ILogger _logger;
private readonly ICacheAdder<Stream> _bitmapsCache;
private readonly OriginalCampaignsProvider _originalCampaignsProvider;
private readonly MetadataProvider _metadataProvider;

private readonly Dictionary<AddonId, BaseAddon> _campaignsCache = [];
private readonly Dictionary<AddonId, BaseAddon> _mapsCache = [];
private readonly Dictionary<AddonId, BaseAddon> _modsCache = [];

private readonly SemaphoreSlim _semaphore = new(1);

private volatile bool _isCacheUpdating;

public event AddonChanged? AddonsChangedEvent;

[Obsolete($"Don't create directly. Use {nameof(InstalledAddonsProviderFactory)}.")]
Expand All @@ -44,14 +44,18 @@ public InstalledAddonsProvider(
IConfigProvider config,
ILogger logger,
[FromKeyedServices("Bitmaps")] ICacheAdder<Stream> bitmapsCache,
OriginalCampaignsProvider originalCampaignsProvider
OriginalCampaignsProvider originalCampaignsProvider,
MetadataProvider metadataProvider
)
{
_game = game;
_config = config;
_logger = logger;
_bitmapsCache = bitmapsCache;
_originalCampaignsProvider = originalCampaignsProvider;
_metadataProvider = metadataProvider;

_metadataProvider.MetadataUpdatedEvent += OnMetadataUpdatedAsync;
}


Expand Down Expand Up @@ -112,7 +116,6 @@ public async Task<bool> CopyAddonIntoFolder(string pathToFile)
return true;
}


/// <summary>
/// Create cache of installed addons.
/// </summary>
Expand All @@ -122,8 +125,6 @@ public async Task CreateCache(bool createNew, AddonTypeEnum addonType)
{
await _semaphore.WaitAsync().ConfigureAwait(false);

_isCacheUpdating = true;

var cache = addonType switch
{
AddonTypeEnum.TC => _campaignsCache,
Expand Down Expand Up @@ -238,7 +239,6 @@ public async Task CreateCache(bool createNew, AddonTypeEnum addonType)
}
finally
{
_isCacheUpdating = false;
_ = _semaphore.Release();
ArgumentNullException.ThrowIfNull(_campaignsCache);
ArgumentNullException.ThrowIfNull(_mapsCache);
Expand Down Expand Up @@ -431,66 +431,88 @@ public IReadOnlyDictionary<AddonId, BaseAddon> GetInstalledAddonsByType(AddonTyp
};
}


private IReadOnlyDictionary<AddonId, BaseAddon> GetInstalledCampaigns()
{
var campaigns = _originalCampaignsProvider.GetOriginalCampaigns(_game);

if (_isCacheUpdating)
if (!_semaphore.Wait(1))
{
return campaigns;
}

ArgumentNullException.ThrowIfNull(_campaignsCache);

if (_campaignsCache.Count == 0)
try
{
return campaigns;
}
ArgumentNullException.ThrowIfNull(_campaignsCache);

if (_game.GameEnum is GameEnum.Wang)
{
//hack to make SW addons appear at the top of the list
foreach (var customCamp in _campaignsCache
.OrderByDescending(static x => x.Key.Id.Equals(nameof(WangAddonEnum.TwinDragon), StringComparison.OrdinalIgnoreCase))
.ThenByDescending(static x => x.Key.Id.Equals(nameof(WangAddonEnum.Wanton), StringComparison.OrdinalIgnoreCase))
.ThenBy(static x => x.Value.Title))
if (_campaignsCache.Count == 0)
{
campaigns.Add(customCamp.Key, customCamp.Value);
return campaigns;
}
}
else
{
foreach (var customCamp in _campaignsCache.OrderBy(static x => x.Value.Title))

if (_game.GameEnum is GameEnum.Wang)
{
//hack to make SW addons appear at the top of the list
foreach (var customCamp in _campaignsCache
.OrderByDescending(static x => x.Key.Id.Equals(nameof(WangAddonEnum.TwinDragon), StringComparison.OrdinalIgnoreCase))
.ThenByDescending(static x => x.Key.Id.Equals(nameof(WangAddonEnum.Wanton), StringComparison.OrdinalIgnoreCase))
.ThenBy(static x => x.Value.Title))
{
campaigns.Add(customCamp.Key, customCamp.Value);
}
}
else
{
campaigns.Add(customCamp.Key, customCamp.Value);
foreach (var customCamp in _campaignsCache.OrderBy(static x => x.Value.Title))
{
campaigns.Add(customCamp.Key, customCamp.Value);
}
}
}

return campaigns;
return campaigns;
}
finally
{
_semaphore.Release();
}
}

private IReadOnlyDictionary<AddonId, BaseAddon> GetInstalledMaps()
{
if (_isCacheUpdating)
if (!_semaphore.Wait(1))
{
return new Dictionary<AddonId, BaseAddon>();
}

ArgumentNullException.ThrowIfNull(_mapsCache);
try
{
ArgumentNullException.ThrowIfNull(_mapsCache);

return _mapsCache;
return _mapsCache;
}
finally
{
_semaphore.Release();
}
}

private IReadOnlyDictionary<AddonId, BaseAddon> GetInstalledMods()
{
if (_isCacheUpdating)
if (!_semaphore.Wait(1))
{
return new Dictionary<AddonId, BaseAddon>();
}

ArgumentNullException.ThrowIfNull(_modsCache);
try
{
ArgumentNullException.ThrowIfNull(_modsCache);

return _modsCache;
return _modsCache;
}
finally
{
_semaphore.Release();
}
}

/// <summary>
Expand Down Expand Up @@ -672,7 +694,8 @@ newAddon.AddonId.Version is not null &&
IsUnpacked = false,
Executables = null,
Options = null,
IsFavorite = _config.FavoriteAddons.Contains(id)
IsFavorite = _config.FavoriteAddons.Contains(id),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

return [addon];
Expand Down Expand Up @@ -797,7 +820,8 @@ newAddon.AddonId.Version is not null &&
IsUnpacked = carcass.IsUnpacked,
Executables = null,
Options = null,
IsFavorite = _config.FavoriteAddons.Contains(id)
IsFavorite = _config.FavoriteAddons.Contains(id),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

addons.Add(addon);
Expand Down Expand Up @@ -835,7 +859,8 @@ or GameEnum.NAM
IsUnpacked = carcass.IsUnpacked,
Executables = carcass.Executables,
Options = carcass.Options,
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version))
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version)),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

addons.Add(addon);
Expand Down Expand Up @@ -863,7 +888,8 @@ or GameEnum.NAM
IsUnpacked = carcass.IsUnpacked,
Executables = carcass.Executables,
Options = carcass.Options,
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version))
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version)),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

addons.Add(addon);
Expand Down Expand Up @@ -894,7 +920,8 @@ or GameEnum.NAM
IsUnpacked = carcass.IsUnpacked,
Executables = carcass.Executables,
Options = carcass.Options,
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version))
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version)),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

addons.Add(addon);
Expand Down Expand Up @@ -922,7 +949,8 @@ or GameEnum.NAM
IsUnpacked = carcass.IsUnpacked,
Executables = carcass.Executables,
Options = carcass.Options,
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version))
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version)),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

addons.Add(addon);
Expand Down Expand Up @@ -950,7 +978,8 @@ or GameEnum.NAM
IsUnpacked = carcass.IsUnpacked,
Executables = carcass.Executables,
Options = carcass.Options,
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version))
IsFavorite = _config.FavoriteAddons.Contains(new(carcass.Id, carcass.Version)),
IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(pathToFile),
};

addons.Add(addon);
Expand Down Expand Up @@ -1177,6 +1206,57 @@ private AddonCarcass GetCarcass(

return carcass;
}


private async void OnMetadataUpdatedAsync(object? sender, ValueTuple<GameEnum, AddonTypeEnum, string>? e)
{
if (e is not null
&& _game.GameEnum != e.Value.Item1)
{
return;
}

IEnumerable<BaseAddon> allAddons = [.. _campaignsCache.Values, .. _mapsCache.Values, .. _modsCache.Values];

foreach (var camp in allAddons)
{
if (camp.PathToFile is null)
{
continue;
}

if (e is null)
{
camp.IsMetadataUpdateAvailable = _metadataProvider.IsMetadataUpdateAvailable(camp.PathToFile);
}
else if (camp.PathToFile.Equals(e.Value.Item3)
&& camp.IsMetadataUpdateAvailable
&& !_metadataProvider.IsMetadataUpdateAvailable(camp.PathToFile))
{
_campaignsCache.Remove(camp.AddonId);
await AddAddonAsync(e.Value.Item3).ConfigureAwait(false);

AddonsChangedEvent?.Invoke(_game.GameEnum, camp.Type);
}
}

if (e is null)
{
AddonsChangedEvent?.Invoke(_game.GameEnum, AddonTypeEnum.TC);
AddonsChangedEvent?.Invoke(_game.GameEnum, AddonTypeEnum.Map);
AddonsChangedEvent?.Invoke(_game.GameEnum, AddonTypeEnum.Mod);
}
else
{
AddonsChangedEvent?.Invoke(_game.GameEnum, e.Value.Item2);
}
}


public void Dispose()
{
_metadataProvider.MetadataUpdatedEvent -= OnMetadataUpdatedAsync;
}
}

internal struct AddonCarcass
Expand Down
Loading
Loading