diff options
Diffstat (limited to 'src/SMAPI.Web/Framework/Clients/UpdateManifest')
8 files changed, 163 insertions, 105 deletions
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 { - /// <summary>An HTTP client for fetching an update manifest from an arbitrary URL.</summary> + /// <summary>An API client for fetching update metadata from an arbitrary JSON URL.</summary> 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 { - /// <summary> - /// A <see cref="JsonMediaTypeFormatter"/> that can parse from content of type <c>text/plain</c>. - /// </summary> - internal class TextAsJsonMediaTypeFormatter : JsonMediaTypeFormatter { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// <summary>A <see cref="JsonMediaTypeFormatter"/> that can parse from content of type <c>text/plain</c>.</summary> + internal class TextAsJsonMediaTypeFormatter : JsonMediaTypeFormatter + { + /********* + ** Public methods + *********/ /// <summary>Construct a new <see cref="JsonMediaTypeFormatter"/></summary> - 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 { - /// <summary>An HTTP client for fetching an update manifest from an arbitrary URL.</summary> - internal class UpdateManifestClient : IUpdateManifestClient { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// <summary>An API client for fetching update metadata from an arbitrary JSON URL.</summary> + internal class UpdateManifestClient : IUpdateManifestClient + { /********* ** Fields *********/ /// <summary>The underlying HTTP client.</summary> private readonly IClient Client; + /********* ** Accessors *********/ @@ -26,30 +28,35 @@ namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { *********/ /// <summary>Construct an instance.</summary> /// <param name="userAgent">The user agent for the API client.</param> - public UpdateManifestClient(string userAgent) { + public UpdateManifestClient(string userAgent) + { this.Client = new FluentClient() .SetUserAgent(userAgent); this.Client.Formatters.Add(new TextAsJsonMediaTypeFormatter()); } /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> - public void Dispose() { + public void Dispose() + { this.Client.Dispose(); } /// <inheritdoc/> - public async Task<IModPage?> GetModData(string id) { + public async Task<IModPage?> GetModData(string id) + { UpdateManifestModel? manifest; - try { + try + { manifest = await this.Client.GetAsync(id).As<UpdateManifestModel?>(); - } 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 +{ /// <summary>Metadata about a mod download in an update manifest file.</summary> - internal class UpdateManifestModDownload : GenericModDownload { - /// <summary>The subkey for this mod download</summary> - private readonly string subkey; + internal class UpdateManifestModDownload : GenericModDownload + { + /********* + ** Fields + *********/ + /// <summary>The update subkey for this mod download.</summary> + private readonly string Subkey; + + + /********* + ** Public methods + *********/ /// <summary>Construct an instance.</summary> - /// <param name="subkey">The subkey for this download.</param> + /// <param name="fieldName">The field name for this mod download in the manifest.</param> /// <param name="name">The mod name for this download.</param> /// <param name="version">The download's version.</param> /// <param name="url">The download's URL.</param> - 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; } - /// <summary> - /// Returns <see langword="true"/> iff the given subkey is the same as the subkey for this download. - /// </summary> - /// <param name="subkey">The subkey to match</param> - /// <returns><see langword="true"/> if <paramref name="subkey"/> is the same as the subkey for this download, <see langword="false"/> otherwise.</returns> - public override bool MatchesSubkey(string subkey) { - return this.subkey == subkey; + /// <summary>Get whether the subkey matches this download.</summary> + /// <param name="subkey">The update subkey to check.</param> + 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 { - /// <summary>Data model for a mod in an update manifest.</summary> - internal class UpdateManifestModModel { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// <summary>The data model for a mod in an update manifest file.</summary> + internal class UpdateManifestModModel + { + /********* + ** Accessors + *********/ /// <summary>The mod's name.</summary> - public string Name { get; } + public string? Name { get; } - /// <summary>The mod's URL.</summary> + /// <summary>The mod page URL from which to download updates.</summary> public string? Url { get; } - /// <summary>The versions for this mod.</summary> - public UpdateManifestVersionModel[] Versions { get; } + /// <summary>The available versions for this mod.</summary> + public UpdateManifestVersionModel[]? Versions { get; } + + /********* + ** Public methods + *********/ /// <summary>Construct an instance.</summary> /// <param name="name">The mod's name.</param> - /// <param name="url">The mod's URL.</param> - /// <param name="versions">The versions for this mod.</param> - public UpdateManifestModModel(string name, string? url, UpdateManifestVersionModel[] versions) { + /// <param name="url">The mod page URL from which to download updates.</param> + /// <param name="versions">The available versions for this mod.</param> + 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 +{ /// <summary>Metadata about an update manifest "page".</summary> - internal class UpdateManifestModPage : GenericModPage { - /// <summary>The update manifest model.</summary> - private UpdateManifestModel manifest; - - /// <summary>Constuct an instance.</summary> - /// <param name="id">The "id" (i.e., URL) of this update manifest.</param> - /// <param name="manifest">The manifest object model.</param> - public UpdateManifestModPage(string id, UpdateManifestModel manifest) : base(ModSiteKey.UpdateManifest, id) { + internal class UpdateManifestModPage : GenericModPage + { + /********* + ** Fields + *********/ + /// <summary>The mods from the update manifest.</summary> + private readonly IDictionary<string, UpdateManifestModModel> Mods; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="url">The URL of the update manifest file.</param> + /// <param name="manifest">The parsed update manifest.</param> + 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<string, UpdateManifestModModel>(); + this.SetInfo(name: url, url: url, version: null, downloads: this.ParseDownloads(manifest.Mods).ToArray()); } /// <summary>Return the mod name for the given subkey, if it exists in this update manifest.</summary> /// <param name="subkey">The subkey.</param> /// <returns>The mod name for the given subkey, or <see langword="null"/> if this manifest does not contain the given subkey.</returns> - 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; } /// <summary>Return the mod URL for the given subkey, if it exists in this update manifest.</summary> /// <param name="subkey">The subkey.</param> /// <returns>The mod URL for the given subkey, or <see langword="null"/> if this manifest does not contain the given subkey.</returns> - 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 *********/ - /// <summary>Translate the downloads from the manifest's object model into <see cref="IModDownload"/> objects.</summary> - /// <param name="manifest">The manifest object model.</param> - /// <returns>An <see cref="IModDownload"/> for each <see cref="UpdateManifestVersionModel"/> in the manifest.</returns> - private static IEnumerable<IModDownload> 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); - } + /// <summary>Convert the raw download info from an update manifest to <see cref="IModDownload"/>.</summary> + /// <param name="mods">The mods from the update manifest.</param> + private IEnumerable<IModDownload> ParseDownloads(IDictionary<string, UpdateManifestModModel>? 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 { - /// <summary>Data model for an update manifest.</summary> - internal class UpdateManifestModel { +namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest +{ + /// <summary>The data model for an update manifest file.</summary> + internal class UpdateManifestModel + { + /********* + ** Accessors + *********/ /// <summary>The manifest format version.</summary> - public string ManifestVersion { get; } + public string? ManifestVersion { get; } - /// <summary>The subkeys in this update manifest.</summary> - public IDictionary<string, UpdateManifestModModel> Subkeys { get; } + /// <summary>The mod info in this update manifest.</summary> + public IDictionary<string, UpdateManifestModModel>? Mods { get; } + + /********* + ** Public methods + *********/ /// <summary>Construct an instance.</summary> /// <param name="manifestVersion">The manifest format version.</param> - /// <param name="subkeys">The subkeys in this update manifest.</param> - public UpdateManifestModel(string manifestVersion, IDictionary<string, UpdateManifestModModel> subkeys) { + /// <param name="mods">The mod info in this update manifest.</param> + public UpdateManifestModel(string manifestVersion, IDictionary<string, UpdateManifestModModel> 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 +{ /// <summary>Data model for a Version in an update manifest.</summary> - internal class UpdateManifestVersionModel { - /// <summary>The semantic version string.</summary> - public string Version { get; } + internal class UpdateManifestVersionModel + { + /********* + ** Accessors + *********/ + /// <summary>The mod's semantic version.</summary> + public string? Version { get; } /// <summary>The URL for this version's download page (if any).</summary> public string? DownloadPageUrl { get; } @@ -12,15 +16,19 @@ namespace StardewModdingAPI.Web.Framework.Clients.UpdateManifest { /// <summary>The URL for this version's direct file download (if any).</summary> public string? DownloadFileUrl { get; } + + /********* + ** Public methods + *********/ /// <summary>Construct an instance.</summary> - /// <param name="version">The semantic version string.</param> - /// <param name="downloadPageUrl">This version's download page URL (if any).</param> - /// <param name="downloadFileUrl">This version's direct file download URL (if any).</param> - public UpdateManifestVersionModel(string version, string? downloadPageUrl, string? downloadFileUrl) { + /// <param name="version">The mod's semantic version.</param> + /// <param name="downloadPageUrl">This version's download page URL, if any.</param> + /// <param name="downloadFileUrl">This version's direct file download URL, if any.</param> + public UpdateManifestVersionModel(string version, string? downloadPageUrl, string? downloadFileUrl) + { this.Version = version; this.DownloadPageUrl = downloadPageUrl; this.DownloadFileUrl = downloadFileUrl; } } } - |