From 55fd4839da43e7ca205eaa85480786e3dfe8af6f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 29 Jan 2023 16:37:22 -0500 Subject: minor formatting, copyediting, and error-handling --- .../Framework/UpdateData/ModSiteKey.cs | 2 +- .../Framework/UpdateData/UpdateKey.cs | 47 ++++++------- src/SMAPI.Web/Controllers/ModsApiController.cs | 6 +- .../Framework/Clients/GenericModDownload.cs | 27 ++++---- src/SMAPI.Web/Framework/Clients/GenericModPage.cs | 19 ++--- .../UpdateManifest/IUpdateManifestClient.cs | 3 +- .../UpdateManifest/TextAsJsonMediaTypeFormatter.cs | 18 +++-- .../Clients/UpdateManifest/UpdateManifestClient.cs | 37 ++++++---- .../UpdateManifest/UpdateManifestModDownload.cs | 40 ++++++----- .../UpdateManifest/UpdateManifestModModel.cs | 32 +++++---- .../UpdateManifest/UpdateManifestModPage.cs | 80 +++++++++++++--------- .../Clients/UpdateManifest/UpdateManifestModel.cs | 30 +++++--- .../UpdateManifest/UpdateManifestVersionModel.cs | 28 +++++--- src/SMAPI.Web/Framework/IModDownload.cs | 13 ++-- src/SMAPI.Web/Framework/IModPage.cs | 18 ++--- src/SMAPI.Web/Framework/ModInfoModel.cs | 37 ++++++---- src/SMAPI.Web/Framework/ModSiteManager.cs | 72 ++++++++++++------- 17 files changed, 293 insertions(+), 216 deletions(-) diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs index d1dd9049..195b0367 100644 --- a/src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs +++ b/src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData /// The Nexus Mods mod repository. Nexus, - /// An arbitrary URL for an update manifest file. + /// An arbitrary URL to a JSON file containing update data. UpdateManifest } } diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs index 7c2cc73c..3e8064fd 100644 --- a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs +++ b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs @@ -54,29 +54,6 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData public UpdateKey(ModSiteKey site, string? id, string? subkey) : this(UpdateKey.GetString(site, id, subkey), site, id, subkey) { } - /// - /// Split a string into two at a delimiter. If the delimiter does not appear in the string then the second - /// value of the returned tuple is null. Both returned strings are trimmed of whitespace. - /// - /// The string to split. - /// The character on which to split. - /// - /// If true then the second string returned will include the delimiter character - /// (provided that the string is not null) - /// - /// - /// A pair containing the string consisting of all characters in before the first - /// occurrence of , and a string consisting of all characters in - /// after the first occurrence of or null if the delimiter does not - /// exist in s. Both strings are trimmed of whitespace. - /// - private static (string, string?) Bifurcate(string s, char delimiter, bool keepDelimiter = false) { - int pos = s.IndexOf(delimiter); - if (pos < 0) - return (s.Trim(), null); - return (s.Substring(0, pos).Trim(), s.Substring(pos + (keepDelimiter ? 0 : 1)).Trim()); - } - /// Parse a raw update key. /// The raw update key to parse. public static UpdateKey Parse(string? raw) @@ -84,14 +61,14 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData if (raw is null) return new UpdateKey(raw, ModSiteKey.Unknown, null, null); // extract site + ID - (string rawSite, string? id) = Bifurcate(raw, ':'); - if (string.IsNullOrWhiteSpace(id)) + (string rawSite, string? id) = UpdateKey.SplitTwoParts(raw, ':'); + if (string.IsNullOrEmpty(id)) id = null; // extract subkey string? subkey = null; if (id != null) - (id, subkey) = Bifurcate(id, '@', true); + (id, subkey) = UpdateKey.SplitTwoParts(id, '@', true); // parse if (!Enum.TryParse(rawSite, true, out ModSiteKey site)) @@ -160,5 +137,23 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData { return $"{site}:{id}{subkey}".Trim(); } + + + /********* + ** Private methods + *********/ + /// Split a string into two parts at a delimiter and trim whitespace. + /// The string to split. + /// The character on which to split. + /// Whether to include the delimiter in the second string. + /// Returns a tuple containing the two strings, with the second value null if the delimiter wasn't found. + private static (string, string?) SplitTwoParts(string str, char delimiter, bool keepDelimiter = false) + { + int splitIndex = str.IndexOf(delimiter); + + return splitIndex >= 0 + ? (str.Substring(0, splitIndex).Trim(), str.Substring(splitIndex + (keepDelimiter ? 0 : 1)).Trim()) + : (str.Trim(), null); + } } } diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 1c34f2af..5fc4987d 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -64,7 +64,7 @@ namespace StardewModdingAPI.Web.Controllers /// The GitHub API client. /// The ModDrop API client. /// The Nexus API client. - /// The UpdateManifest client. + /// The API client for arbitrary update manifest URLs. public ModsApiController(IWebHostEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions config, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus, IUpdateManifestClient updateManifest) { this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json")); @@ -163,9 +163,9 @@ namespace StardewModdingAPI.Web.Controllers // if there's only a prerelease version (e.g. from GitHub), don't override the main version ISemanticVersion? curMain = data.Version; - string? curMainUrl = data.MainVersionUrl; ISemanticVersion? curPreview = data.PreviewVersion; - string? curPreviewUrl = data.PreviewVersionUrl; + string? curMainUrl = data.MainModPageUrl; + string? curPreviewUrl = data.PreviewModPageUrl; if (curPreview == null && curMain?.IsPrerelease() == true) { curPreview = curMain; diff --git a/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs b/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs index 5cc03aba..6c9c08ef 100644 --- a/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs +++ b/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs @@ -17,10 +17,9 @@ namespace StardewModdingAPI.Web.Framework.Clients /// The download's file version. public string? Version { get; } - /// - /// The URL for this download, if it has one distinct from the mod page's URL. - /// - public string? Url { get; } + /// The mod URL page from which to download this update, if different from the URL of the mod page it was fetched from. + public string? ModPageUrl { get; } + /********* ** Public methods @@ -29,23 +28,21 @@ namespace StardewModdingAPI.Web.Framework.Clients /// The download's display name. /// The download's description. /// The download's file version. - /// The download's URL (if different from the mod page's URL). - public GenericModDownload(string name, string? description, string? version, string? url = null) + /// The mod URL page from which to download this update, if different from the URL of the mod page it was fetched from. + public GenericModDownload(string name, string? description, string? version, string? modPageUrl = null) { this.Name = name; this.Description = description; this.Version = version; - this.Url = url; + this.ModPageUrl = modPageUrl; } - /// - /// Return if the subkey matches this download. A subkey matches if it appears as - /// a substring in the name or description. - /// - /// the subkey - /// if matches this download, otherwise - public virtual bool MatchesSubkey(string subkey) { - return this.Name.Contains(subkey, StringComparison.OrdinalIgnoreCase) == true + /// Get whether the subkey matches this download. + /// The update subkey to check. + public virtual bool MatchesSubkey(string subkey) + { + return + this.Name.Contains(subkey, StringComparison.OrdinalIgnoreCase) || this.Description?.Contains(subkey, StringComparison.OrdinalIgnoreCase) == true; } } diff --git a/src/SMAPI.Web/Framework/Clients/GenericModPage.cs b/src/SMAPI.Web/Framework/Clients/GenericModPage.cs index 4c66e1a0..e939f1d8 100644 --- a/src/SMAPI.Web/Framework/Clients/GenericModPage.cs +++ b/src/SMAPI.Web/Framework/Clients/GenericModPage.cs @@ -40,9 +40,10 @@ namespace StardewModdingAPI.Web.Framework.Clients [MemberNotNullWhen(true, nameof(IModPage.Name), nameof(IModPage.Url))] public bool IsValid => this.Status == RemoteModStatus.Ok; - /// Whether to use strict subkey matching or not. + /// Whether this mod page requires string subkey matching, in which case a subkey that isn't found will return no update instead of falling back to one without. public bool IsSubkeyStrict { get; set; } = false; + /********* ** Public methods *********/ @@ -82,17 +83,17 @@ namespace StardewModdingAPI.Web.Framework.Clients return this; } - /// Returns the mod page name. - /// ignored - /// The mod page name. - public virtual string? GetName(string? subkey) { + /// Get the mod name for an update subkey, if different from the mod page name. + /// The update subkey. + public virtual string? GetName(string? subkey) + { return this.Name; } - /// Returns the mod page URL. - /// ignored - /// The mod page URL. - public virtual string? GetUrl(string? subkey) { + /// Get the mod page URL for an update subkey, if different from the mod page it was fetched from. + /// The update subkey. + public virtual string? GetUrl(string? subkey) + { return this.Url; } } diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/IUpdateManifestClient.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/IUpdateManifestClient.cs index 36d018c7..dd9f5811 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/IUpdateManifestClient.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/IUpdateManifestClient.cs @@ -3,7 +3,6 @@ using System; namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { - /// An HTTP client for fetching an update manifest from an arbitrary URL. + /// An API client for fetching update metadata from an arbitrary JSON URL. internal interface IUpdateManifestClient : IModSiteClient, IDisposable { } } - diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/TextAsJsonMediaTypeFormatter.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/TextAsJsonMediaTypeFormatter.cs index 48e3c294..02722cb1 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/TextAsJsonMediaTypeFormatter.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/TextAsJsonMediaTypeFormatter.cs @@ -1,14 +1,18 @@ -// Copyright 2022 Jamie Taylor +// Copyright 2022 Jamie Taylor using System.Net.Http.Formatting; using System.Net.Http.Headers; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { - /// - /// A that can parse from content of type text/plain. - /// - internal class TextAsJsonMediaTypeFormatter : JsonMediaTypeFormatter { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// A that can parse from content of type text/plain. + internal class TextAsJsonMediaTypeFormatter : JsonMediaTypeFormatter + { + /********* + ** Public methods + *********/ /// Construct a new - public TextAsJsonMediaTypeFormatter() { + public TextAsJsonMediaTypeFormatter() + { this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain")); } } diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestClient.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestClient.cs index d7cf4945..88a5c2f6 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestClient.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestClient.cs @@ -1,19 +1,21 @@ // Copyright 2022 Jamie Taylor -using System; +using System.Net; +using System.Threading.Tasks; using Pathoschild.Http.Client; using StardewModdingAPI.Toolkit.Framework.UpdateData; -using System.Threading.Tasks; -using System.Net; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { - /// An HTTP client for fetching an update manifest from an arbitrary URL. - internal class UpdateManifestClient : IUpdateManifestClient { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// An API client for fetching update metadata from an arbitrary JSON URL. + internal class UpdateManifestClient : IUpdateManifestClient + { /********* ** Fields *********/ /// The underlying HTTP client. private readonly IClient Client; + /********* ** Accessors *********/ @@ -26,30 +28,35 @@ namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { *********/ /// Construct an instance. /// The user agent for the API client. - public UpdateManifestClient(string userAgent) { + public UpdateManifestClient(string userAgent) + { this.Client = new FluentClient() .SetUserAgent(userAgent); this.Client.Formatters.Add(new TextAsJsonMediaTypeFormatter()); } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - public void Dispose() { + public void Dispose() + { this.Client.Dispose(); } /// - public async Task GetModData(string id) { + public async Task GetModData(string id) + { UpdateManifestModel? manifest; - try { + try + { manifest = await this.Client.GetAsync(id).As(); - } catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound) { - return new GenericModPage(this.SiteKey, id).SetError(RemoteModStatus.DoesNotExist, $"No update manifest found at {id}"); } - if (manifest is null) { - return new GenericModPage(this.SiteKey, id).SetError(RemoteModStatus.DoesNotExist, $"Error parsing manifest at {id}"); + catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound) + { + return new GenericModPage(this.SiteKey, id).SetError(RemoteModStatus.DoesNotExist, $"No update manifest found at {id}"); } - return new UpdateManifestModPage(id, manifest); + return manifest is not null + ? new UpdateManifestModPage(id, manifest) + : new GenericModPage(this.SiteKey, id).SetError(RemoteModStatus.DoesNotExist, $"The update manifest at {id} has an invalid format"); } } } diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModDownload.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModDownload.cs index 117ae15c..0a6d4736 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModDownload.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModDownload.cs @@ -1,27 +1,35 @@ // Copyright 2022 Jamie Taylor -using System; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ /// Metadata about a mod download in an update manifest file. - internal class UpdateManifestModDownload : GenericModDownload { - /// The subkey for this mod download - private readonly string subkey; + internal class UpdateManifestModDownload : GenericModDownload + { + /********* + ** Fields + *********/ + /// The update subkey for this mod download. + private readonly string Subkey; + + + /********* + ** Public methods + *********/ /// Construct an instance. - /// The subkey for this download. + /// The field name for this mod download in the manifest. /// The mod name for this download. /// The download's version. /// The download's URL. - public UpdateManifestModDownload(string subkey, string name, string? version, string? url) : base(name, null, version, url) { - this.subkey = subkey; + public UpdateManifestModDownload(string fieldName, string name, string? version, string? url) + : base(name, null, version, url) + { + this.Subkey = fieldName; } - /// - /// Returns iff the given subkey is the same as the subkey for this download. - /// - /// The subkey to match - /// if is the same as the subkey for this download, otherwise. - public override bool MatchesSubkey(string subkey) { - return this.subkey == subkey; + /// Get whether the subkey matches this download. + /// The update subkey to check. + public override bool MatchesSubkey(string subkey) + { + return subkey == this.Subkey; } } } - diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModModel.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModModel.cs index 4ec9c03d..92642321 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModModel.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModModel.cs @@ -1,26 +1,34 @@ // Copyright 2022 Jamie Taylor -using System; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { - /// Data model for a mod in an update manifest. - internal class UpdateManifestModModel { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// The data model for a mod in an update manifest file. + internal class UpdateManifestModModel + { + /********* + ** Accessors + *********/ /// The mod's name. - public string Name { get; } + public string? Name { get; } - /// The mod's URL. + /// The mod page URL from which to download updates. public string? Url { get; } - /// The versions for this mod. - public UpdateManifestVersionModel[] Versions { get; } + /// The available versions for this mod. + public UpdateManifestVersionModel[]? Versions { get; } + + /********* + ** Public methods + *********/ /// Construct an instance. /// The mod's name. - /// The mod's URL. - /// The versions for this mod. - public UpdateManifestModModel(string name, string? url, UpdateManifestVersionModel[] versions) { + /// The mod page URL from which to download updates. + /// The available versions for this mod. + public UpdateManifestModModel(string? name, string? url, UpdateManifestVersionModel[]? versions) + { this.Name = name; this.Url = url; this.Versions = versions; } } } - diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModPage.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModPage.cs index 109175b5..bbc7b5da 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModPage.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModPage.cs @@ -1,58 +1,74 @@ -// Copyright 2022 Jamie Taylor -using System; +// Copyright 2022 Jamie Taylor using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Toolkit.Framework.UpdateData; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ /// Metadata about an update manifest "page". - internal class UpdateManifestModPage : GenericModPage { - /// The update manifest model. - private UpdateManifestModel manifest; - - /// Constuct an instance. - /// The "id" (i.e., URL) of this update manifest. - /// The manifest object model. - public UpdateManifestModPage(string id, UpdateManifestModel manifest) : base(ModSiteKey.UpdateManifest, id) { + internal class UpdateManifestModPage : GenericModPage + { + /********* + ** Fields + *********/ + /// The mods from the update manifest. + private readonly IDictionary Mods; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The URL of the update manifest file. + /// The parsed update manifest. + public UpdateManifestModPage(string url, UpdateManifestModel manifest) + : base(ModSiteKey.UpdateManifest, url) + { this.IsSubkeyStrict = true; - this.manifest = manifest; - this.SetInfo(name: id, url: id, version: null, downloads: TranslateDownloads(manifest).ToArray()); + this.Mods = manifest.Mods ?? new Dictionary(); + this.SetInfo(name: url, url: url, version: null, downloads: this.ParseDownloads(manifest.Mods).ToArray()); } /// Return the mod name for the given subkey, if it exists in this update manifest. /// The subkey. /// The mod name for the given subkey, or if this manifest does not contain the given subkey. - public override string? GetName(string? subkey) { - if (subkey is null) - return null; - this.manifest.Subkeys.TryGetValue(subkey, out UpdateManifestModModel? modModel); - return modModel?.Name; + public override string? GetName(string? subkey) + { + return subkey is not null && this.Mods.TryGetValue(subkey, out UpdateManifestModModel? modModel) + ? modModel.Name + : null; } /// Return the mod URL for the given subkey, if it exists in this update manifest. /// The subkey. /// The mod URL for the given subkey, or if this manifest does not contain the given subkey. - public override string? GetUrl(string? subkey) { - if (subkey is null) - return null; - this.manifest.Subkeys.TryGetValue(subkey, out UpdateManifestModModel? modModel); - return modModel?.Url; + public override string? GetUrl(string? subkey) + { + return subkey is not null && this.Mods.TryGetValue(subkey, out UpdateManifestModModel? modModel) + ? modModel.Url + : null; } /********* ** Private methods *********/ - /// Translate the downloads from the manifest's object model into objects. - /// The manifest object model. - /// An for each in the manifest. - private static IEnumerable TranslateDownloads(UpdateManifestModel manifest) { - foreach (var entry in manifest.Subkeys) { - foreach (var version in entry.Value.Versions) { - yield return new UpdateManifestModDownload(entry.Key, entry.Value.Name, version.Version, version.DownloadFileUrl ?? version.DownloadPageUrl); - } + /// Convert the raw download info from an update manifest to . + /// The mods from the update manifest. + private IEnumerable ParseDownloads(IDictionary? mods) + { + if (mods is null) + yield break; + + foreach ((string modKey, UpdateManifestModModel mod) in mods) + { + if (mod.Versions is null) + continue; + + foreach (UpdateManifestVersionModel version in mod.Versions) + yield return new UpdateManifestModDownload(modKey, mod.Name ?? modKey, version.Version, version.DownloadFileUrl ?? version.DownloadPageUrl); } } } -} \ No newline at end of file +} diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModel.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModel.cs index 03f89726..ad618022 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModel.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestModel.cs @@ -1,23 +1,31 @@ // Copyright 2022 Jamie Taylor -using System; using System.Collections.Generic; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { - /// Data model for an update manifest. - internal class UpdateManifestModel { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// The data model for an update manifest file. + internal class UpdateManifestModel + { + /********* + ** Accessors + *********/ /// The manifest format version. - public string ManifestVersion { get; } + public string? ManifestVersion { get; } - /// The subkeys in this update manifest. - public IDictionary Subkeys { get; } + /// The mod info in this update manifest. + public IDictionary? Mods { get; } + + /********* + ** Public methods + *********/ /// Construct an instance. /// The manifest format version. - /// The subkeys in this update manifest. - public UpdateManifestModel(string manifestVersion, IDictionary subkeys) { + /// The mod info in this update manifest. + public UpdateManifestModel(string manifestVersion, IDictionary mods) + { this.ManifestVersion = manifestVersion; - this.Subkeys = subkeys; + this.Mods = mods; } } } - diff --git a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestVersionModel.cs b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestVersionModel.cs index 55b6db61..90e054d8 100644 --- a/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestVersionModel.cs +++ b/src/SMAPI.Web/Framework/Clients/UpdateManifest/UpdateManifestVersionModel.cs @@ -1,10 +1,14 @@ // Copyright 2022 Jamie Taylor -using System; -namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ /// Data model for a Version in an update manifest. - internal class UpdateManifestVersionModel { - /// The semantic version string. - public string Version { get; } + internal class UpdateManifestVersionModel + { + /********* + ** Accessors + *********/ + /// The mod's semantic version. + public string? Version { get; } /// The URL for this version's download page (if any). public string? DownloadPageUrl { get; } @@ -12,15 +16,19 @@ namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { /// The URL for this version's direct file download (if any). public string? DownloadFileUrl { get; } + + /********* + ** Public methods + *********/ /// Construct an instance. - /// The semantic version string. - /// This version's download page URL (if any). - /// This version's direct file download URL (if any). - public UpdateManifestVersionModel(string version, string? downloadPageUrl, string? downloadFileUrl) { + /// The mod's semantic version. + /// This version's download page URL, if any. + /// This version's direct file download URL, if any. + public UpdateManifestVersionModel(string version, string? downloadPageUrl, string? downloadFileUrl) + { this.Version = version; this.DownloadPageUrl = downloadPageUrl; this.DownloadFileUrl = downloadFileUrl; } } } - diff --git a/src/SMAPI.Web/Framework/IModDownload.cs b/src/SMAPI.Web/Framework/IModDownload.cs index b0e9a664..8cb82989 100644 --- a/src/SMAPI.Web/Framework/IModDownload.cs +++ b/src/SMAPI.Web/Framework/IModDownload.cs @@ -15,12 +15,15 @@ namespace StardewModdingAPI.Web.Framework /// The download's file version. string? Version { get; } - /// This download's URL (if it has a URL that is different from the containing mod page's URL). - string? Url { get; } + /// The mod URL page from which to download this update, if different from the URL of the mod page it was fetched from. + string? ModPageUrl { get; } - /// Return iff the subkey matches this download - /// the subkey - /// if matches this download, otherwise + + /********* + ** Methods + *********/ + /// Get whether the subkey matches this download. + /// The update subkey to check. bool MatchesSubkey(string subkey); } } diff --git a/src/SMAPI.Web/Framework/IModPage.cs b/src/SMAPI.Web/Framework/IModPage.cs index 3fc8bb81..ef1513eb 100644 --- a/src/SMAPI.Web/Framework/IModPage.cs +++ b/src/SMAPI.Web/Framework/IModPage.cs @@ -39,25 +39,19 @@ namespace StardewModdingAPI.Web.Framework [MemberNotNullWhen(false, nameof(IModPage.Error))] bool IsValid { get; } - /// - /// Does this page use strict subkey matching. Pages that use string subkey matching do not fall back - /// to searching for versions without a subkey if there are no versions found when given a subkey. - /// Additionally, the leading @ is stripped from the subkey value before searching for matches. - /// + /// Whether this mod page requires string subkey matching, in which case a subkey that isn't found will return no update instead of falling back to one without. Additionally, the leading @ is stripped from the subkey value before searching for matches. bool IsSubkeyStrict { get; } + /********* ** Methods *********/ - - /// Get the mod name associated with the given subkey, if any. - /// The subkey. - /// The mod name associated with the given subkey (if any) + /// Get the mod name for an update subkey, if different from the mod page name. + /// The update subkey. string? GetName(string? subkey); - /// Get the URL for the mod associated with the given subkey, if any. - /// The subkey. - /// The URL for the mod associated with the given subkey (if any) + /// Get the mod page URL for an update subkey, if different from the mod page it was fetched from. + /// The update subkey. string? GetUrl(string? subkey); /// Set the fetched mod info. diff --git a/src/SMAPI.Web/Framework/ModInfoModel.cs b/src/SMAPI.Web/Framework/ModInfoModel.cs index 17415589..502c0827 100644 --- a/src/SMAPI.Web/Framework/ModInfoModel.cs +++ b/src/SMAPI.Web/Framework/ModInfoModel.cs @@ -27,11 +27,12 @@ namespace StardewModdingAPI.Web.Framework /// The error message indicating why the mod is invalid (if applicable). public string? Error { get; private set; } - /// The URL associated with the mod's latest version (if distinct from the mod page's URL). - public string? MainVersionUrl { get; private set; } + /// The mod page URL from which can be downloaded, if different from the . + public string? MainModPageUrl { get; private set; } + + /// The mod page URL from which can be downloaded, if different from the . + public string? PreviewModPageUrl { get; private set; } - /// The URL associated with the mod's (if distinct from the mod page's URL). - public string? PreviewVersionUrl { get; private set; } /********* ** Public methods @@ -51,7 +52,8 @@ namespace StardewModdingAPI.Web.Framework { this .SetBasicInfo(name, url) - .SetVersions(version!, previewVersion) + .SetMainVersion(version!) + .SetPreviewVersion(previewVersion) .SetError(status, error!); } @@ -67,18 +69,25 @@ namespace StardewModdingAPI.Web.Framework return this; } - /// Set the mod version info. - /// The semantic version for the mod's latest release. - /// The semantic version for the mod's latest preview release, if available and different from . - /// The URL associated with , if different from the mod page's URL. - /// The URL associated with , if different from the mod page's URL. + /// Set the mod's main version info. + /// The semantic version for the mod's latest stable release. + /// The mod page URL from which can be downloaded, if different from the . [MemberNotNull(nameof(ModInfoModel.Version))] - public ModInfoModel SetVersions(ISemanticVersion version, ISemanticVersion? previewVersion = null, string? mainVersionUrl = null, string? previewVersionUrl = null) + public ModInfoModel SetMainVersion(ISemanticVersion version, string? modPageUrl = null) { this.Version = version; - this.PreviewVersion = previewVersion; - this.MainVersionUrl = mainVersionUrl; - this.PreviewVersionUrl = previewVersionUrl; + this.MainModPageUrl = modPageUrl; + + return this; + } + + /// Set the mod's preview version info. + /// The semantic version for the mod's latest preview release. + /// The mod page URL from which can be downloaded, if different from the . + public ModInfoModel SetPreviewVersion(ISemanticVersion? version, string? modPageUrl = null) + { + this.PreviewVersion = version; + this.PreviewModPageUrl = modPageUrl; return this; } diff --git a/src/SMAPI.Web/Framework/ModSiteManager.cs b/src/SMAPI.Web/Framework/ModSiteManager.cs index 5581d799..350159a3 100644 --- a/src/SMAPI.Web/Framework/ModSiteManager.cs +++ b/src/SMAPI.Web/Framework/ModSiteManager.cs @@ -66,25 +66,34 @@ namespace StardewModdingAPI.Web.Framework { // get base model ModInfoModel model = new(); - if (!page.IsValid) { + if (!page.IsValid) + { model.SetError(page.Status, page.Error); return model; } - if (page.IsSubkeyStrict && subkey is not null) { - if (subkey.Length > 0 && subkey[0] == '@') { + // trim subkey in strict mode + if (page.IsSubkeyStrict && subkey is not null) + { + if (subkey.StartsWith('@')) subkey = subkey.Substring(1); - } } // fetch versions - bool hasVersions = this.TryGetLatestVersions(page, subkey, allowNonStandardVersions, mapRemoteVersions, out ISemanticVersion? mainVersion, out ISemanticVersion? previewVersion, out string? mainVersionUrl, out string? previewVersionUrl); + bool hasVersions = this.TryGetLatestVersions(page, subkey, allowNonStandardVersions, mapRemoteVersions, out ISemanticVersion? mainVersion, out ISemanticVersion? previewVersion, out string? mainModPageUrl, out string? previewModPageUrl); if (!hasVersions) return model.SetError(RemoteModStatus.InvalidData, $"The {page.Site} mod with ID '{page.Id}{subkey ?? ""}' has no valid versions."); - model.SetBasicInfo(page.GetName(subkey) ?? page.Name, page.GetUrl(subkey) ?? page.Url); + // apply mod page info + model.SetBasicInfo( + name: page.GetName(subkey) ?? page.Name, + url: page.GetUrl(subkey) ?? page.Url + ); + // return info - return model.SetVersions(mainVersion!, previewVersion, mainVersionUrl, previewVersionUrl); + return model + .SetMainVersion(mainVersion!, mainModPageUrl) + .SetPreviewVersion(previewVersion, previewModPageUrl); } /// Get a semantic local version for update checks. @@ -115,12 +124,14 @@ namespace StardewModdingAPI.Web.Framework /// The changes to apply to remote versions for update checks. /// The main mod version. /// The latest prerelease version, if newer than . - private bool TryGetLatestVersions(IModPage? mod, string? subkey, bool allowNonStandardVersions, ChangeDescriptor? mapRemoteVersions, [NotNullWhen(true)] out ISemanticVersion? main, out ISemanticVersion? preview, out string? mainVersionUrl, out string? previewVersionUrl) + /// The mod page URL from which can be downloaded, if different from the 's URL. + /// The mod page URL from which can be downloaded, if different from the 's URL. + private bool TryGetLatestVersions(IModPage? mod, string? subkey, bool allowNonStandardVersions, ChangeDescriptor? mapRemoteVersions, [NotNullWhen(true)] out ISemanticVersion? main, out ISemanticVersion? preview, out string? mainModPageUrl, out string? previewModPageUrl) { main = null; preview = null; - mainVersionUrl = null; - previewVersionUrl = null; + mainModPageUrl = null; + previewModPageUrl = null; // parse all versions from the mod page IEnumerable<(IModDownload? download, ISemanticVersion? version)> GetAllVersions() @@ -152,12 +163,12 @@ namespace StardewModdingAPI.Web.Framework .ToArray(); // get main + preview versions - void TryGetVersions([NotNullWhen(true)] out ISemanticVersion? mainVersion, out ISemanticVersion? previewVersion, out string? mainVersionUrl, out string? previewVersionUrl, Func<(IModDownload? download, ISemanticVersion? version), bool>? filter = null) + void TryGetVersions([NotNullWhen(true)] out ISemanticVersion? mainVersion, out ISemanticVersion? previewVersion, out string? mainUrl, out string? previewUrl, Func<(IModDownload? download, ISemanticVersion? version), bool>? filter = null) { mainVersion = null; previewVersion = null; - mainVersionUrl = null; - previewVersionUrl = null; + mainUrl = null; + previewUrl = null; // get latest main + preview version foreach ((IModDownload? download, ISemanticVersion? version) entry in versions) @@ -165,14 +176,18 @@ namespace StardewModdingAPI.Web.Framework if (entry.version is null || filter?.Invoke(entry) == false) continue; - if (entry.version.IsPrerelease()) { - if (previewVersion is null) { + if (entry.version.IsPrerelease()) + { + if (previewVersion is null) + { previewVersion = entry.version; - previewVersionUrl = entry.download?.Url; + previewUrl = entry.download?.ModPageUrl; } - } else { + } + else + { mainVersion = entry.version; - mainVersionUrl = entry.download?.Url; + mainUrl = entry.download?.ModPageUrl; break; // any others will be older since entries are sorted by version } } @@ -180,26 +195,31 @@ namespace StardewModdingAPI.Web.Framework // normalize values if (previewVersion is not null) { - if (mainVersion is null) { + if (mainVersion is null) + { // if every version is prerelease, latest one is the main version mainVersion = previewVersion; - mainVersionUrl = previewVersionUrl; + mainUrl = previewUrl; } - if (!previewVersion.IsNewerThan(mainVersion)) { + if (!previewVersion.IsNewerThan(mainVersion)) + { previewVersion = null; - previewVersionUrl = null; + previewUrl = null; } } } - if (subkey is not null) { - TryGetVersions(out main, out preview, out mainVersionUrl, out previewVersionUrl, entry => entry.download?.MatchesSubkey(subkey) == true); + // get versions for subkey + if (subkey is not null) + { + TryGetVersions(out main, out preview, out mainModPageUrl, out previewModPageUrl, filter: entry => entry.download?.MatchesSubkey(subkey) == true); if (mod?.IsSubkeyStrict == true) return main != null; } - if (main is null) - TryGetVersions(out main, out preview, out mainVersionUrl, out previewVersionUrl); + // fallback to non-subkey versions + if (main is null) + TryGetVersions(out main, out preview, out mainModPageUrl, out previewModPageUrl); return main != null; } -- cgit