From 4a5d72f223c82a1052af9536b8d512cac597e765 Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 19 Jun 2024 11:01:23 +0200 Subject: [PATCH 1/4] Use mod name from SMAPI API --- .../Diagnostics.cs | 9 ++-- .../Emitters/DependencyDiagnosticEmitter.cs | 19 +++++--- ...dDatabaseCompatibilityDiagnosticEmitter.cs | 7 +-- .../Emitters/VersionDiagnosticEmitter.cs | 11 +++-- .../WebAPI/ISMAPIWebApi.cs | 14 +++++- .../WebAPI/SMAPIWebApi.cs | 46 +++++++++++-------- 6 files changed, 68 insertions(+), 38 deletions(-) diff --git a/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs b/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs index 5d26584681..77ead2f08b 100644 --- a/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs +++ b/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs @@ -17,11 +17,14 @@ internal static partial class Diagnostics .WithId(new DiagnosticId(Source, number: 1)) .WithTitle("Missing required dependency") .WithSeverity(DiagnosticSeverity.Warning) - .WithSummary("Mod {Mod} is missing required dependency '{MissingDependency}'") - .WithDetails("You can download the latest version at {NexusModsDependencyUri}.") + .WithSummary("Mod {Mod} is missing required dependency {MissingDependencyModName}") + .WithDetails(""" +You can download the latest version of {MissingDependencyModName} ({MissingDependencyModId}) at {NexusModsDependencyUri}. +""") .WithMessageData(messageBuilder => messageBuilder .AddDataReference("Mod") - .AddValue("MissingDependency") + .AddValue("MissingDependencyModName") + .AddValue("MissingDependencyModId") .AddValue("NexusModsDependencyUri") ) .Finish(); diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs index c7c1dfbfb0..9e6134666b 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; using System.Runtime.CompilerServices; +using DynamicData.Kernel; +using Metsys.Bson; using Microsoft.Extensions.Logging; using NexusMods.Abstractions.Diagnostics; using NexusMods.Abstractions.Diagnostics.Emitters; @@ -146,7 +148,7 @@ private async Task> DiagnoseMissingDependencies( cancellationToken.ThrowIfCancellationRequested(); - var missingDependencyUris = await _smapiWebApi.GetModPageUrls( + var apiMods = await _smapiWebApi.GetModDetails( os: _os, gameVersion, smapiVersion, @@ -159,12 +161,17 @@ private async Task> DiagnoseMissingDependencies( return missingDependencies.Select(missingDependency => { var mod = ModForId(loadout, modId); + var modDetails = apiMods.GetValueOrDefault(missingDependency); + // TODO: diagnostic even if the API doesn't return anything + if (modDetails?.Name is null) return null; + return Diagnostics.CreateMissingRequiredDependency( Mod: mod.ToReference(loadout), - MissingDependency: missingDependency, - NexusModsDependencyUri: missingDependencyUris.GetValueOrDefault(missingDependency, Helpers.NexusModsLink) + MissingDependencyModId: modDetails.UniqueId, + MissingDependencyModName: modDetails.Name, + NexusModsDependencyUri: modDetails.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ); - }); + }).Where(x => x is not null).Select(x => x!); }); } @@ -220,7 +227,7 @@ private async Task> DiagnoseOutdatedDependencies( cancellationToken.ThrowIfCancellationRequested(); - var missingDependencyUris = await _smapiWebApi.GetModPageUrls( + var apiMods = await _smapiWebApi.GetModDetails( os: _os, gameVersion, smapiVersion, @@ -232,7 +239,7 @@ private async Task> DiagnoseOutdatedDependencies( Dependency: ModForId(loadout, tuple.DependencyModId).ToReference(loadout), MinimumVersion: tuple.MinimumVersion.ToString(), CurrentVersion: tuple.CurrentVersion.ToString(), - NexusModsDependencyUri: missingDependencyUris.GetValueOrDefault(tuple.DependencyId, Helpers.NexusModsLink) + NexusModsDependencyUri: apiMods.GetValueOrDefault(tuple.DependencyId)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink )); } diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs index 6309d75167..a4a47d845e 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using DynamicData.Kernel; using Microsoft.Extensions.Logging; using NexusMods.Abstractions.Diagnostics; using NexusMods.Abstractions.Diagnostics.Emitters; @@ -92,8 +93,8 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume list.Add((mod, manifest, versionedFields)); } - var modPageUrls = await _smapiWebApi.GetModPageUrls( - _os, + var apiMods = await _smapiWebApi.GetModDetails( + os: _os, gameVersion, smapiVersion, smapiIDs: list.Select(tuple => tuple.Item2.UniqueID).ToArray() @@ -116,7 +117,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume yield return Diagnostics.CreateModCompatabilityAssumeBroken( Mod: mod.ToReference(loadout), ReasonPhrase: reasonPhrase ?? "it's no longer compatible", - ModLink: modPageUrls.GetValueOrDefault(manifest.UniqueID, DefaultWikiLink), + ModLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink, ModVersion: manifest.Version.ToString() ); } diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs index 6c004ac8a9..cdbf413313 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using DynamicData.Kernel; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using NexusMods.Abstractions.Diagnostics; @@ -49,11 +50,11 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume .GetAllManifestsAsync(_logger, _fileStore, loadout, onlyEnabledMods: true, cancellationToken) .ToArrayAsync(cancellationToken); - var modPageUrls = await _smapiWebApi.GetModPageUrls( - _os, + var apiMods = await _smapiWebApi.GetModDetails( + os: _os, gameVersion, smapiVersion, - smapiMods.Select(tuple => tuple.Item2.UniqueID).ToArray() + smapiIDs: smapiMods.Select(tuple => tuple.Item2.UniqueID).ToArray() ); foreach (var tuple in smapiMods) @@ -70,7 +71,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume ModName: mod.Name, MinimumAPIVersion: minimumApiVersion.ToString(), CurrentSMAPIVersion: smapiVersion.ToString(), - NexusModsLink: modPageUrls.GetValueOrDefault(manifest.UniqueID, Helpers.NexusModsLink), + NexusModsLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink, SMAPINexusModsLink: Helpers.SMAPILink ); } @@ -82,7 +83,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume ModName: mod.Name, MinimumGameVersion: minimumGameVersion.ToString(), CurrentGameVersion: gameVersion.ToString(), - NexusModsLink: modPageUrls.GetValueOrDefault(manifest.UniqueID, Helpers.NexusModsLink) + NexusModsLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink ); } } diff --git a/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs b/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs index a5ed08f22a..46aebadbda 100644 --- a/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs +++ b/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs @@ -1,3 +1,4 @@ +using DynamicData.Kernel; using JetBrains.Annotations; using NexusMods.Abstractions.Diagnostics.Values; using NexusMods.Paths; @@ -12,12 +13,21 @@ namespace NexusMods.Games.StardewValley.WebAPI; public interface ISMAPIWebApi : IDisposable { /// - /// Gets all mod page urls of the given mods. + /// Fetches details for mods using their IDs. /// - public Task> GetModPageUrls( + public Task> GetModDetails( IOSInformation os, ISemanticVersion gameVersion, ISemanticVersion smapiVersion, string[] smapiIDs ); } + +public record SMAPIWebApiMod +{ + public required string UniqueId { get; init; } + + public required string? Name { get; init; } + + public required Optional NexusModsLink { get; init; } +} diff --git a/src/Games/NexusMods.Games.StardewValley/WebAPI/SMAPIWebApi.cs b/src/Games/NexusMods.Games.StardewValley/WebAPI/SMAPIWebApi.cs index 33bb0342b2..886c406131 100644 --- a/src/Games/NexusMods.Games.StardewValley/WebAPI/SMAPIWebApi.cs +++ b/src/Games/NexusMods.Games.StardewValley/WebAPI/SMAPIWebApi.cs @@ -22,14 +22,14 @@ internal sealed class SMAPIWebApi : ISMAPIWebApi private bool _isDisposed; private WebApiClient? _client; - private ImmutableDictionary _knownModPageUrls = ImmutableDictionary.Empty.WithComparers(StringComparer.OrdinalIgnoreCase); + private ImmutableDictionary _cache = ImmutableDictionary.Empty.WithComparers(StringComparer.OrdinalIgnoreCase); public SMAPIWebApi(ILogger logger) { _logger = logger; } - public async Task> GetModPageUrls( + public async Task> GetModDetails( IOSInformation os, ISemanticVersion gameVersion, ISemanticVersion smapiVersion, @@ -45,7 +45,7 @@ public async Task> GetModPageUrls( ); var mods = smapiIDs - .Where(id => !_knownModPageUrls.ContainsKey(id)) + .Where(id => !_cache.ContainsKey(id)) .Select(id => new ModSearchEntryModel( id: id, installedVersion: null, @@ -76,36 +76,44 @@ public async Task> GetModPageUrls( if (apiResult is not null) { var tmp = apiResult - .Select, ValueTuple>>(kv => + .Select(kv => { var (id, model) = kv; var metadata = model.Metadata; var nexusId = metadata?.NexusID; - if (nexusId is null) return (null, Optional.None); - - var uri = NexusModsUrlBuilder.CreateDiagnosticUri(StardewValley.GameDomain.Value, nexusId.Value.ToString()); - return (id, uri.WithName("Nexus Mods")); + var nexusModsLink = Optional.None; + + if (nexusId is not null) + { + var uri = NexusModsUrlBuilder.CreateDiagnosticUri(StardewValley.GameDomain.Value, nexusId.Value.ToString()); + nexusModsLink = uri.WithName("Nexus Mods"); + } + + return new SMAPIWebApiMod + { + UniqueId = id, + Name = metadata?.Name, + NexusModsLink = nexusModsLink, + }; }) - .Where(kv => kv.Item1 is not null) - .Select(tuple => new KeyValuePair(tuple.Item1!, tuple.Item2.Value)) - .ToDictionary(); + .ToDictionary(x => x.UniqueId, x => x, StringComparer.OrdinalIgnoreCase); - ImmutableDictionary initial, updated; + ImmutableDictionary initial, updated; do { - initial = _knownModPageUrls; - updated = _knownModPageUrls.SetItems(tmp); - } while (initial != Interlocked.CompareExchange(ref _knownModPageUrls, updated, initial)); + initial = _cache; + updated = _cache.SetItems(tmp); + } while (initial != Interlocked.CompareExchange(ref _cache, updated, initial)); } } return smapiIDs .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(id => (Id: id, Link: _knownModPageUrls.GetValueOrDefault(id))) - .Where(tuple => tuple.Link != default(NamedLink)) - .Select(tuple => new KeyValuePair(tuple.Id, tuple.Link)) - .ToDictionary(StringComparer.OrdinalIgnoreCase); + .Select(id => _cache.GetValueOrDefault(id)) + .Where(mod => mod is not null) + .Select(mod => mod!) + .ToDictionary(mod => mod.UniqueId, mod => mod, StringComparer.OrdinalIgnoreCase); } private static Platform ToPlatform(IOSInformation os) From 9712e30cc61f61ba69c881a9dd5b8586dc83a456 Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 19 Jun 2024 11:11:14 +0200 Subject: [PATCH 2/4] Update mod name formatting --- src/Games/NexusMods.Games.StardewValley/Diagnostics.cs | 4 ++-- .../DiagnosticSystem/ModReferenceFormatter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs b/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs index 77ead2f08b..30d6fe8f74 100644 --- a/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs +++ b/src/Games/NexusMods.Games.StardewValley/Diagnostics.cs @@ -17,9 +17,9 @@ internal static partial class Diagnostics .WithId(new DiagnosticId(Source, number: 1)) .WithTitle("Missing required dependency") .WithSeverity(DiagnosticSeverity.Warning) - .WithSummary("Mod {Mod} is missing required dependency {MissingDependencyModName}") + .WithSummary("Mod {Mod} is missing required dependency '{MissingDependencyModName}'") .WithDetails(""" -You can download the latest version of {MissingDependencyModName} ({MissingDependencyModId}) at {NexusModsDependencyUri}. +You can download the latest version of '{MissingDependencyModName}' (`{MissingDependencyModId}`) at {NexusModsDependencyUri}. """) .WithMessageData(messageBuilder => messageBuilder .AddDataReference("Mod") diff --git a/src/NexusMods.App.UI/DiagnosticSystem/ModReferenceFormatter.cs b/src/NexusMods.App.UI/DiagnosticSystem/ModReferenceFormatter.cs index c8b9f687eb..3497941ce2 100644 --- a/src/NexusMods.App.UI/DiagnosticSystem/ModReferenceFormatter.cs +++ b/src/NexusMods.App.UI/DiagnosticSystem/ModReferenceFormatter.cs @@ -12,6 +12,6 @@ public void Format(IDiagnosticWriter writer, ref DiagnosticWriterState state, Mo { // TODO: custom markdown control var mod = conn.Db.Get(value.DataId); - writer.Write(ref state, mod?.Name ?? "MISSING MOD"); + writer.Write(ref state, $"'{mod.Name}'"); } } From bc8eeaf78da3d3e329701e9d6e5c09d8779749e0 Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 19 Jun 2024 11:51:40 +0200 Subject: [PATCH 3/4] Use wiki link --- .../Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs index a4a47d845e..8e9714e444 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs @@ -117,7 +117,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume yield return Diagnostics.CreateModCompatabilityAssumeBroken( Mod: mod.ToReference(loadout), ReasonPhrase: reasonPhrase ?? "it's no longer compatible", - ModLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink, + ModLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => DefaultWikiLink) ?? DefaultWikiLink, ModVersion: manifest.Version.ToString() ); } From 91262cb356e221d4b38b8dd5849d695c3251cce8 Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 19 Jun 2024 12:06:09 +0200 Subject: [PATCH 4/4] Refactor --- .../Emitters/DependencyDiagnosticEmitter.cs | 2 +- ...SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs | 2 +- .../Emitters/VersionDiagnosticEmitter.cs | 4 ++-- .../WebAPI/ISMAPIWebApi.cs | 12 ++++++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs index 9e6134666b..8a0e6fa138 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs @@ -239,7 +239,7 @@ private async Task> DiagnoseOutdatedDependencies( Dependency: ModForId(loadout, tuple.DependencyModId).ToReference(loadout), MinimumVersion: tuple.MinimumVersion.ToString(), CurrentVersion: tuple.CurrentVersion.ToString(), - NexusModsDependencyUri: apiMods.GetValueOrDefault(tuple.DependencyId)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink + NexusModsDependencyUri: apiMods.GetLink(tuple.DependencyId, defaultValue: Helpers.NexusModsLink) )); } diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs index 8e9714e444..0eac6c2f5d 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs @@ -117,7 +117,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume yield return Diagnostics.CreateModCompatabilityAssumeBroken( Mod: mod.ToReference(loadout), ReasonPhrase: reasonPhrase ?? "it's no longer compatible", - ModLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => DefaultWikiLink) ?? DefaultWikiLink, + ModLink:apiMods.GetLink(manifest.UniqueID, defaultValue: DefaultWikiLink), ModVersion: manifest.Version.ToString() ); } diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs index cdbf413313..5199775372 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs @@ -71,7 +71,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume ModName: mod.Name, MinimumAPIVersion: minimumApiVersion.ToString(), CurrentSMAPIVersion: smapiVersion.ToString(), - NexusModsLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink, + NexusModsLink: apiMods.GetLink(manifest.UniqueID, defaultValue: Helpers.NexusModsLink), SMAPINexusModsLink: Helpers.SMAPILink ); } @@ -83,7 +83,7 @@ public async IAsyncEnumerable Diagnose(Loadout.Model loadout, [Enume ModName: mod.Name, MinimumGameVersion: minimumGameVersion.ToString(), CurrentGameVersion: gameVersion.ToString(), - NexusModsLink: apiMods.GetValueOrDefault(manifest.UniqueID)?.NexusModsLink.ValueOr(() => Helpers.NexusModsLink) ?? Helpers.NexusModsLink + NexusModsLink: apiMods.GetLink(manifest.UniqueID, defaultValue: Helpers.NexusModsLink) ); } } diff --git a/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs b/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs index 46aebadbda..506a2a40fb 100644 --- a/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs +++ b/src/Games/NexusMods.Games.StardewValley/WebAPI/ISMAPIWebApi.cs @@ -31,3 +31,15 @@ public record SMAPIWebApiMod public required Optional NexusModsLink { get; init; } } + +public static class SMAPIWebApiExtensions +{ + public static NamedLink GetLink(this IReadOnlyDictionary mods, string id, NamedLink defaultValue) + { + var mod = mods.GetValueOrDefault(id); + if (mod is null) return defaultValue; + + var link = mod.NexusModsLink; + return link.HasValue ? link.Value : defaultValue; + } +}