From 2c02dfe45a6d1252bfef557db8f39f97e57d3d19 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 00:47:46 -0400 Subject: [PATCH] rewrite to make update-check logic vendor-agnostic (#336) --- .../Controllers/CheckController.cs | 73 ++++++++++++++++--- .../{ => ModRepositories}/IModRepository.cs | 11 ++- .../NexusRepository.cs} | 25 ++++--- .../Models/ModGenericModel.cs | 37 +++++----- .../Models/ModSearchModel.cs | 6 +- 5 files changed, 105 insertions(+), 47 deletions(-) rename src/StardewModdingAPI.Web/Framework/{ => ModRepositories}/IModRepository.cs (60%) rename src/StardewModdingAPI.Web/Framework/{NexusModsClient.cs => ModRepositories/NexusRepository.cs} (75%) diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs index 9ec068cb..2a346cf3 100644 --- a/src/StardewModdingAPI.Web/Controllers/CheckController.cs +++ b/src/StardewModdingAPI.Web/Controllers/CheckController.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using StardewModdingAPI.Web.Framework; +using StardewModdingAPI.Web.Framework.ModRepositories; using StardewModdingAPI.Web.Models; namespace StardewModdingAPI.Web.Controllers @@ -11,28 +13,75 @@ namespace StardewModdingAPI.Web.Controllers [Route("api/check")] public class CheckController : Controller { + /********* + ** Properties + *********/ + /// The mod repositories which provide mod metadata. + private readonly IDictionary Repositories = + new IModRepository[] + { + new NexusRepository() + } + .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); + + /********* ** Public methods *********/ /// Fetch version metadata for the given mods. - /// The mods for which to fetch update metadata. + /// The mod update search criteria. [HttpPost] - public async Task Post([FromBody] ModSearchModel[] mods) + public async Task Post([FromBody] ModSearchModel search) { - using (NexusModsClient client = new NexusModsClient()) - { - List result = new List(); + IList result = new List(); - foreach (ModSearchModel mod in mods) + foreach (string modKey in search.ModKeys) + { + // parse mod key + if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) { - if (mod.NexusID.HasValue) - result.Add(await client.GetModInfoAsync(mod.NexusID.Value)); - else - result.Add(new ModGenericModel(null, mod.NexusID ?? 0)); + result.Add(new ModGenericModel(modKey, "The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'.")); + continue; } - return result.ToArray(); + // get matching repository + if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository)) + { + result.Add(new ModGenericModel(modKey, "There's no mod repository matching this namespaced mod ID.")); + continue; + } + + // fetch mod info + result.Add(await repository.GetModInfoAsync(modID)); } + + return result.ToArray(); + } + + + /********* + ** Private methods + *********/ + /// Parse a namespaced mod ID. + /// The raw mod ID to parse. + /// The parsed vendor key. + /// The parsed mod ID. + /// Returns whether the value could be parsed. + private bool TryParseModKey(string raw, out string vendorKey, out string modID) + { + // split parts + string[] parts = raw?.Split(':'); + if (parts == null || parts.Length != 2) + { + vendorKey = null; + modID = null; + return false; + } + + // parse + vendorKey = parts[0]; + modID = parts[1]; + return true; } } } diff --git a/src/StardewModdingAPI.Web/Framework/IModRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs similarity index 60% rename from src/StardewModdingAPI.Web/Framework/IModRepository.cs rename to src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs index ebf9850f..43bad4e9 100644 --- a/src/StardewModdingAPI.Web/Framework/IModRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs @@ -2,16 +2,23 @@ using System; using System.Threading.Tasks; using StardewModdingAPI.Web.Models; -namespace StardewModdingAPI.Web.Framework +namespace StardewModdingAPI.Web.Framework.ModRepositories { /// A repository which provides mod metadata. internal interface IModRepository : IDisposable { + /********* + ** Accessors + *********/ + /// The unique key for this vendor. + string VendorKey { get; } + + /********* ** Public methods *********/ /// Get metadata about a mod in the repository. /// The mod ID in this repository. - Task GetModInfoAsync(int id); + Task GetModInfoAsync(string id); } } diff --git a/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs similarity index 75% rename from src/StardewModdingAPI.Web/Framework/NexusModsClient.cs rename to src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs index 8f010d56..37f309da 100644 --- a/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -4,10 +4,10 @@ using Newtonsoft.Json; using Pathoschild.Http.Client; using StardewModdingAPI.Web.Models; -namespace StardewModdingAPI.Web.Framework +namespace StardewModdingAPI.Web.Framework.ModRepositories { /// An HTTP client for fetching mod metadata from Nexus Mods. - internal class NexusModsClient : IModRepository + internal class NexusRepository : IModRepository { /********* ** Properties @@ -15,11 +15,19 @@ namespace StardewModdingAPI.Web.Framework /// The underlying HTTP client. private readonly IClient Client; + + /********* + ** Accessors + *********/ + /// The unique key for this vendor. + public string VendorKey { get; } = "Nexus"; + + /********* ** Public methods *********/ /// Construct an instance. - public NexusModsClient() + public NexusRepository() { this.Client = new FluentClient("http://www.nexusmods.com/stardewvalley") .SetUserAgent("Nexus Client v0.63.15"); @@ -27,18 +35,18 @@ namespace StardewModdingAPI.Web.Framework /// Get metadata about a mod in the repository. /// The mod ID in this repository. - public async Task GetModInfoAsync(int id) + public async Task GetModInfoAsync(string id) { try { NexusResponseModel response = await this.Client .GetAsync($"mods/{id}") .As(); - return new ModGenericModel("Nexus", id, response.Name, response.Version, response.Url); + return new ModGenericModel($"{this.VendorKey}:{id}", response.Name, response.Version, response.Url); } - catch (Exception) + catch (Exception ex) { - return new ModGenericModel("Nexus", id); + return new ModGenericModel($"{this.VendorKey}:{id}", ex.ToString()); } } @@ -58,9 +66,6 @@ namespace StardewModdingAPI.Web.Framework /********* ** Accessors *********/ - /// The unique mod ID. - public int ID { get; set; } - /// The mod name. public string Name { get; set; } diff --git a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs index dc36c7f4..88a6e4bd 100644 --- a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json; + namespace StardewModdingAPI.Web.Models { /// Generic metadata about a mod. @@ -6,53 +8,48 @@ namespace StardewModdingAPI.Web.Models /********* ** Accessors *********/ - /// The unique mod ID. - public int ID { get; } + /// The namespaced mod key. + public string ModKey { get; } /// The mod name. public string Name { get; } - /// The mod's vendor ID. - public string Vendor { get; } - /// The mod's semantic version number. public string Version { get; } /// The mod's web URL. public string Url { get; } - /// Whether the mod is valid. - public bool Valid { get; } + /// The error message indicating why the mod is invalid (if applicable). + public string Error { get; } /********* ** Public methods *********/ /// Construct a valid instance. - /// The mod's vendor ID. - /// The unique mod ID. + /// The namespaced mod key. /// The mod name. /// The mod's semantic version number. /// The mod's web URL. - /// Whether the mod is valid. - public ModGenericModel(string vendor, int id, string name, string version, string url, bool valid = true) + /// The error message indicating why the mod is invalid (if applicable). + [JsonConstructor] + public ModGenericModel(string modKey, string name, string version, string url, string error = null) { - this.Vendor = vendor; - this.ID = id; + this.ModKey = modKey; this.Name = name; this.Version = version; this.Url = url; - this.Valid = valid; + this.Error = error; // mainly initialised here for the JSON deserialiser } /// Construct an valid instance. - /// The mod's vendor ID. - /// The unique mod ID. - public ModGenericModel(string vendor, int id) + /// The namespaced mod key. + /// The error message indicating why the mod is invalid. + public ModGenericModel(string modKey, string error) { - this.Vendor = vendor; - this.ID = id; - this.Valid = false; + this.ModKey = modKey; + this.Error = error; } } } diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs index eb9ac920..852ea439 100644 --- a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs @@ -1,9 +1,9 @@ namespace StardewModdingAPI.Web.Models { - /// The search criteria for a mod. + /// The mod update search criteria. public class ModSearchModel { - /// The Nexus Mods ID (if any). - public int? NexusID { get; set; } + /// The namespaced mod keys to search. + public string[] ModKeys { get; set; } } }