summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-05-23 21:55:11 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-05-23 21:55:11 -0400
commit786077340f2cea37d82455fc413535ae82a912ee (patch)
tree588d6755b1001bd7eb218dcf9b332feb933e180b
parentd7add894419543667e60569bfeb439e8e797a4d1 (diff)
downloadSMAPI-786077340f2cea37d82455fc413535ae82a912ee.tar.gz
SMAPI-786077340f2cea37d82455fc413535ae82a912ee.tar.bz2
SMAPI-786077340f2cea37d82455fc413535ae82a912ee.zip
refactor update check API
This simplifies the logic for individual clients, centralises common logic, and prepares for upcoming features.
-rw-r--r--docs/release-notes.md1
-rw-r--r--src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs (renamed from src/SMAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs)4
-rw-r--r--src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs67
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs146
-rw-r--r--src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs6
-rw-r--r--src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs12
-rw-r--r--src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs38
-rw-r--r--src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishMod.cs18
-rw-r--r--src/SMAPI.Web/Framework/Clients/Chucklefish/IChucklefishClient.cs12
-rw-r--r--src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs72
-rw-r--r--src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeMod.cs23
-rw-r--r--src/SMAPI.Web/Framework/Clients/CurseForge/ICurseForgeClient.cs12
-rw-r--r--src/SMAPI.Web/Framework/Clients/GenericModDownload.cs36
-rw-r--r--src/SMAPI.Web/Framework/Clients/GenericModPage.cs79
-rw-r--r--src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs56
-rw-r--r--src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs2
-rw-r--r--src/SMAPI.Web/Framework/Clients/IModSiteClient.cs23
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/IModDropClient.cs12
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs63
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs21
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs16
-rw-r--r--src/SMAPI.Web/Framework/Clients/Nexus/INexusClient.cs12
-rw-r--r--src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs94
-rw-r--r--src/SMAPI.Web/Framework/Clients/Nexus/ResponseModels/NexusMod.cs (renamed from src/SMAPI.Web/Framework/Clients/Nexus/NexusMod.cs)11
-rw-r--r--src/SMAPI.Web/Framework/Extensions.cs6
-rw-r--r--src/SMAPI.Web/Framework/IModDownload.cs15
-rw-r--r--src/SMAPI.Web/Framework/IModPage.cs52
-rw-r--r--src/SMAPI.Web/Framework/ModInfoModel.cs (renamed from src/SMAPI.Web/Framework/ModRepositories/ModInfoModel.cs)29
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/BaseRepository.cs51
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs57
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/CurseForgeRepository.cs63
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs82
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/IModRepository.cs24
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs57
-rw-r--r--src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs65
-rw-r--r--src/SMAPI.Web/Framework/ModSiteManager.cs180
-rw-r--r--src/SMAPI.Web/Framework/RemoteModStatus.cs (renamed from src/SMAPI.Web/Framework/ModRepositories/RemoteModStatus.cs)2
37 files changed, 685 insertions, 834 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index f3f2efa4..894fd562 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -24,6 +24,7 @@
* For SMAPI developers:
* Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed.
+ * Overhauled update checks to simplify individual clients, centralize common logic, and enable upcoming features.
* Merged the separate legacy redirects app on AWS into the main app on Azure.
## 3.5
diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs
index 765ca334..47cd3f7e 100644
--- a/src/SMAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs
+++ b/src/SMAPI.Toolkit/Framework/UpdateData/ModSiteKey.cs
@@ -1,7 +1,7 @@
namespace StardewModdingAPI.Toolkit.Framework.UpdateData
{
- /// <summary>A mod repository which SMAPI can check for updates.</summary>
- public enum ModRepositoryKey
+ /// <summary>A mod site which SMAPI can check for updates.</summary>
+ public enum ModSiteKey
{
/// <summary>An unknown or invalid mod repository.</summary>
Unknown,
diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
index 3fc1759e..f6044148 100644
--- a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
+++ b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
@@ -11,8 +11,8 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <summary>The raw update key text.</summary>
public string RawText { get; }
- /// <summary>The mod repository containing the mod.</summary>
- public ModRepositoryKey Repository { get; }
+ /// <summary>The mod site containing the mod.</summary>
+ public ModSiteKey Site { get; }
/// <summary>The mod ID within the repository.</summary>
public string ID { get; }
@@ -26,53 +26,56 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
*********/
/// <summary>Construct an instance.</summary>
/// <param name="rawText">The raw update key text.</param>
- /// <param name="repository">The mod repository containing the mod.</param>
- /// <param name="id">The mod ID within the repository.</param>
- public UpdateKey(string rawText, ModRepositoryKey repository, string id)
+ /// <param name="site">The mod site containing the mod.</param>
+ /// <param name="id">The mod ID within the site.</param>
+ public UpdateKey(string rawText, ModSiteKey site, string id)
{
- this.RawText = rawText;
- this.Repository = repository;
- this.ID = id;
+ this.RawText = rawText?.Trim();
+ this.Site = site;
+ this.ID = id?.Trim();
this.LooksValid =
- repository != ModRepositoryKey.Unknown
+ site != ModSiteKey.Unknown
&& !string.IsNullOrWhiteSpace(id);
}
/// <summary>Construct an instance.</summary>
- /// <param name="repository">The mod repository containing the mod.</param>
- /// <param name="id">The mod ID within the repository.</param>
- public UpdateKey(ModRepositoryKey repository, string id)
- : this($"{repository}:{id}", repository, id) { }
+ /// <param name="site">The mod site containing the mod.</param>
+ /// <param name="id">The mod ID within the site.</param>
+ public UpdateKey(ModSiteKey site, string id)
+ : this(UpdateKey.GetString(site, id), site, id) { }
/// <summary>Parse a raw update key.</summary>
/// <param name="raw">The raw update key to parse.</param>
public static UpdateKey Parse(string raw)
{
- // split parts
- string[] parts = raw?.Split(':');
- if (parts == null || parts.Length != 2)
- return new UpdateKey(raw, ModRepositoryKey.Unknown, null);
-
- // extract parts
- string repositoryKey = parts[0].Trim();
- string id = parts[1].Trim();
+ // extract site + ID
+ string rawSite;
+ string id;
+ {
+ string[] parts = raw?.Trim().Split(':');
+ if (parts == null || parts.Length != 2)
+ return new UpdateKey(raw, ModSiteKey.Unknown, null);
+
+ rawSite = parts[0].Trim();
+ id = parts[1].Trim();
+ }
if (string.IsNullOrWhiteSpace(id))
id = null;
// parse
- if (!Enum.TryParse(repositoryKey, true, out ModRepositoryKey repository))
- return new UpdateKey(raw, ModRepositoryKey.Unknown, id);
+ if (!Enum.TryParse(rawSite, true, out ModSiteKey site))
+ return new UpdateKey(raw, ModSiteKey.Unknown, id);
if (id == null)
- return new UpdateKey(raw, repository, null);
+ return new UpdateKey(raw, site, null);
- return new UpdateKey(raw, repository, id);
+ return new UpdateKey(raw, site, id);
}
/// <summary>Get a string that represents the current object.</summary>
public override string ToString()
{
return this.LooksValid
- ? $"{this.Repository}:{this.ID}"
+ ? UpdateKey.GetString(this.Site, this.ID)
: this.RawText;
}
@@ -82,7 +85,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
{
return
other != null
- && this.Repository == other.Repository
+ && this.Site == other.Site
&& string.Equals(this.ID, other.ID, StringComparison.InvariantCultureIgnoreCase);
}
@@ -97,7 +100,15 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <returns>A hash code for the current object.</returns>
public override int GetHashCode()
{
- return $"{this.Repository}:{this.ID}".ToLower().GetHashCode();
+ return $"{this.Site}:{this.ID}".ToLower().GetHashCode();
+ }
+
+ /// <summary>Get the string representation of an update key.</summary>
+ /// <param name="site">The mod site containing the mod.</param>
+ /// <param name="id">The mod ID within the repository.</param>
+ public static string GetString(ModSiteKey site, string id)
+ {
+ return $"{site}:{id}".Trim();
}
}
}
diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs
index b9d7c32d..14be520d 100644
--- a/src/SMAPI.Web/Controllers/ModsApiController.cs
+++ b/src/SMAPI.Web/Controllers/ModsApiController.cs
@@ -15,13 +15,13 @@ using StardewModdingAPI.Web.Framework;
using StardewModdingAPI.Web.Framework.Caching;
using StardewModdingAPI.Web.Framework.Caching.Mods;
using StardewModdingAPI.Web.Framework.Caching.Wiki;
+using StardewModdingAPI.Web.Framework.Clients;
using StardewModdingAPI.Web.Framework.Clients.Chucklefish;
using StardewModdingAPI.Web.Framework.Clients.CurseForge;
using StardewModdingAPI.Web.Framework.Clients.GitHub;
using StardewModdingAPI.Web.Framework.Clients.ModDrop;
using StardewModdingAPI.Web.Framework.Clients.Nexus;
using StardewModdingAPI.Web.Framework.ConfigModels;
-using StardewModdingAPI.Web.Framework.ModRepositories;
namespace StardewModdingAPI.Web.Controllers
{
@@ -33,8 +33,8 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Fields
*********/
- /// <summary>The mod repositories which provide mod metadata.</summary>
- private readonly IDictionary<ModRepositoryKey, IModRepository> Repositories;
+ /// <summary>The mod sites which provide mod metadata.</summary>
+ private readonly ModSiteManager ModSites;
/// <summary>The cache in which to store wiki data.</summary>
private readonly IWikiCacheRepository WikiCache;
@@ -69,16 +69,7 @@ namespace StardewModdingAPI.Web.Controllers
this.WikiCache = wikiCache;
this.ModCache = modCache;
this.Config = config;
- this.Repositories =
- new IModRepository[]
- {
- new ChucklefishRepository(chucklefish),
- new CurseForgeRepository(curseForge),
- new GitHubRepository(github),
- new ModDropRepository(modDrop),
- new NexusRepository(nexus)
- }
- .ToDictionary(p => p.VendorKey);
+ this.ModSites = new ModSiteManager(new IModSiteClient[] { chucklefish, curseForge, github, modDrop, nexus });
}
/// <summary>Fetch version metadata for the given mods.</summary>
@@ -149,40 +140,18 @@ namespace StardewModdingAPI.Web.Controllers
}
// fetch data
- ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions);
- if (data.Error != null)
+ ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions, wikiEntry?.MapRemoteVersions);
+ if (data.Status != RemoteModStatus.Ok)
{
- errors.Add(data.Error);
+ errors.Add(data.Error ?? data.Status.ToString());
continue;
}
- // handle main version
- if (data.Version != null)
- {
- ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandardVersions);
- if (version == null)
- {
- errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'.");
- continue;
- }
-
- if (this.IsNewer(version, main?.Version))
- main = new ModEntryVersionModel(version, data.Url);
- }
-
- // handle optional version
- if (data.PreviewVersion != null)
- {
- ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandardVersions);
- if (version == null)
- {
- errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'.");
- continue;
- }
-
- if (this.IsNewer(version, optional?.Version))
- optional = new ModEntryVersionModel(version, data.Url);
- }
+ // handle versions
+ if (this.IsNewer(data.Version, main?.Version))
+ main = new ModEntryVersionModel(data.Version, data.Url);
+ if (this.IsNewer(data.PreviewVersion, optional?.Version))
+ optional = new ModEntryVersionModel(data.PreviewVersion, data.Url);
}
// get unofficial version
@@ -222,7 +191,7 @@ namespace StardewModdingAPI.Web.Controllers
}
// get recommended update (if any)
- ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions);
+ ISemanticVersion installedVersion = this.ModSites.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions);
if (apiVersion != null && installedVersion != null)
{
// get newer versions
@@ -282,32 +251,27 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>Get the mod info for an update key.</summary>
/// <param name="updateKey">The namespaced update key.</param>
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
- private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions)
+ /// <param name="mapRemoteVersions">Maps remote versions to a semantic version for update checks.</param>
+ private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions, IDictionary<string, string> mapRemoteVersions)
{
- // get from cache
- if (this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out Cached<ModInfoModel> cachedMod) && !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes))
- return cachedMod.Data;
-
- // fetch from mod site
+ // get mod page
+ IModPage page;
{
- // get site
- if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository))
- return new ModInfoModel().SetError(RemoteModStatus.DoesNotExist, $"There's no mod site with key '{updateKey.Repository}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}].");
+ bool isCached =
+ this.ModCache.TryGetMod(updateKey.Site, updateKey.ID, out Cached<IModPage> cachedMod)
+ && !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes);
- // fetch mod
- ModInfoModel mod = await repository.GetModInfoAsync(updateKey.ID);
- if (mod.Error == null)
+ if (isCached)
+ page = cachedMod.Data;
+ else
{
- if (mod.Version == null)
- mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number.");
- else if (!SemanticVersion.TryParse(mod.Version, allowNonStandardVersions, out _))
- mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{mod.Version}'.");
+ page = await this.ModSites.GetModPageAsync(updateKey);
+ this.ModCache.SaveMod(updateKey.Site, updateKey.ID, page);
}
-
- // cache mod
- this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, mod);
- return mod;
}
+
+ // get version info
+ return this.ModSites.GetPageVersions(page, allowNonStandardVersions, mapRemoteVersions);
}
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary>
@@ -334,13 +298,13 @@ namespace StardewModdingAPI.Web.Controllers
if (entry != null)
{
if (entry.NexusID.HasValue)
- yield return $"{ModRepositoryKey.Nexus}:{entry.NexusID}";
+ yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID?.ToString());
if (entry.ModDropID.HasValue)
- yield return $"{ModRepositoryKey.ModDrop}:{entry.ModDropID}";
+ yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID?.ToString());
if (entry.CurseForgeID.HasValue)
- yield return $"{ModRepositoryKey.CurseForge}:{entry.CurseForgeID}";
+ yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID?.ToString());
if (entry.ChucklefishID.HasValue)
- yield return $"{ModRepositoryKey.Chucklefish}:{entry.ChucklefishID}";
+ yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID?.ToString());
}
}
@@ -355,51 +319,5 @@ namespace StardewModdingAPI.Web.Controllers
yield return key;
}
}
-
- /// <summary>Get a semantic local version for update checks.</summary>
- /// <param name="version">The version to parse.</param>
- /// <param name="map">A map of version replacements.</param>
- /// <param name="allowNonStandard">Whether to allow non-standard versions.</param>
- private ISemanticVersion GetMappedVersion(string version, IDictionary<string, string> map, bool allowNonStandard)
- {
- // try mapped version
- string rawNewVersion = this.GetRawMappedVersion(version, map, allowNonStandard);
- if (SemanticVersion.TryParse(rawNewVersion, allowNonStandard, out ISemanticVersion parsedNew))
- return parsedNew;
-
- // return original version
- return SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsedOld)
- ? parsedOld
- : null;
- }
-
- /// <summary>Get a semantic local version for update checks.</summary>
- /// <param name="version">The version to map.</param>
- /// <param name="map">A map of version replacements.</param>
- /// <param name="allowNonStandard">Whether to allow non-standard versions.</param>
- private string GetRawMappedVersion(string version, IDictionary<string, string> map, bool allowNonStandard)
- {
- if (version == null || map == null || !map.Any())
- return version;
-
- // match exact raw version
- if (map.ContainsKey(version))
- return map[version];
-
- // match parsed version
- if (SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsed))
- {
- if (map.ContainsKey(parsed.ToString()))
- return map[parsed.ToString()];
-
- foreach ((string fromRaw, string toRaw) in map)
- {
- if (SemanticVersion.TryParse(fromRaw, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(toRaw, allowNonStandard, out ISemanticVersion newVersion))
- return newVersion.ToString();
- }
- }
-
- return version;
- }
}
}
diff --git a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs
index 004202f9..0d912c7b 100644
--- a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs
+++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs
@@ -1,6 +1,6 @@
using System;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
-using StardewModdingAPI.Web.Framework.ModRepositories;
+using StardewModdingAPI.Web.Framework.Clients;
namespace StardewModdingAPI.Web.Framework.Caching.Mods
{
@@ -15,13 +15,13 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
/// <param name="mod">The fetched mod.</param>
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
- bool TryGetMod(ModRepositoryKey site, string id, out Cached<ModInfoModel> mod, bool markRequested = true);
+ bool TryGetMod(ModSiteKey site, string id, out Cached<IModPage> mod, bool markRequested = true);
/// <summary>Save data fetched for a mod.</summary>
/// <param name="site">The mod site on which the mod is found.</param>
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
/// <param name="mod">The mod data.</param>
- void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod);
+ void SaveMod(ModSiteKey site, string id, IModPage mod);
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
/// <param name="age">The minimum age for which to remove mods.</param>
diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs
index 62461116..6b0ec1ec 100644
--- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs
+++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
-using StardewModdingAPI.Web.Framework.ModRepositories;
+using StardewModdingAPI.Web.Framework.Clients;
namespace StardewModdingAPI.Web.Framework.Caching.Mods
{
@@ -13,7 +13,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
** Fields
*********/
/// <summary>The cached mod data indexed by <c>{site key}:{ID}</c>.</summary>
- private readonly IDictionary<string, Cached<ModInfoModel>> Mods = new Dictionary<string, Cached<ModInfoModel>>(StringComparer.InvariantCultureIgnoreCase);
+ private readonly IDictionary<string, Cached<IModPage>> Mods = new Dictionary<string, Cached<IModPage>>(StringComparer.InvariantCultureIgnoreCase);
/*********
@@ -24,7 +24,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
/// <param name="mod">The fetched mod.</param>
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
- public bool TryGetMod(ModRepositoryKey site, string id, out Cached<ModInfoModel> mod, bool markRequested = true)
+ public bool TryGetMod(ModSiteKey site, string id, out Cached<IModPage> mod, bool markRequested = true)
{
// get mod
if (!this.Mods.TryGetValue(this.GetKey(site, id), out var cachedMod))
@@ -45,10 +45,10 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
/// <param name="site">The mod site on which the mod is found.</param>
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
/// <param name="mod">The mod data.</param>
- public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod)
+ public void SaveMod(ModSiteKey site, string id, IModPage mod)
{
string key = this.GetKey(site, id);
- this.Mods[key] = new Cached<ModInfoModel>(mod);
+ this.Mods[key] = new Cached<IModPage>(mod);
}
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
@@ -73,7 +73,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
/// <summary>Get a cache key.</summary>
/// <param name="site">The mod site.</param>
/// <param name="id">The mod ID.</param>
- private string GetKey(ModRepositoryKey site, string id)
+ private string GetKey(ModSiteKey site, string id)
{
return $"{site}:{id.Trim()}".ToLower();
}
diff --git a/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs b/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs
index cdb281e2..ca156da4 100644
--- a/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs
+++ b/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs
@@ -3,6 +3,7 @@ using System.Net;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Pathoschild.Http.Client;
+using StardewModdingAPI.Toolkit.Framework.UpdateData;
namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
{
@@ -20,6 +21,13 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
/*********
+ ** Accessors
+ *********/
+ /// <summary>The unique key for the mod site.</summary>
+ public ModSiteKey SiteKey => ModSiteKey.Chucklefish;
+
+
+ /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -32,42 +40,40 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent);
}
- /// <summary>Get metadata about a mod.</summary>
- /// <param name="id">The Chucklefish mod ID.</param>
- /// <returns>Returns the mod info if found, else <c>null</c>.</returns>
- public async Task<ChucklefishMod> GetModAsync(uint id)
+ /// <summary>Get update check info about a mod.</summary>
+ /// <param name="id">The mod ID.</param>
+ public async Task<IModPage> GetModData(string id)
{
+ IModPage page = new GenericModPage(this.SiteKey, id);
+
+ // get mod ID
+ if (!uint.TryParse(id, out uint parsedId))
+ return page.SetError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid Chucklefish mod ID, must be an integer ID.");
+
// fetch HTML
string html;
try
{
html = await this.Client
- .GetAsync(string.Format(this.ModPageUrlFormat, id))
+ .GetAsync(string.Format(this.ModPageUrlFormat, parsedId))
.AsString();
}
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound || ex.Status == HttpStatusCode.Forbidden)
{
- return null;
+ return page.SetError(RemoteModStatus.DoesNotExist, "Found no Chucklefish mod with this ID.");
}
-
- // parse HTML
var doc = new HtmlDocument();
doc.LoadHtml(html);
// extract mod info
- string url = this.GetModUrl(id);
+ string url = this.GetModUrl(parsedId);
string name = doc.DocumentNode.SelectSingleNode("//meta[@name='twitter:title']").Attributes["content"].Value;
if (name.StartsWith("[SMAPI] "))
name = name.Substring("[SMAPI] ".Length);
string version = doc.DocumentNode.SelectSingleNode("//h1/span")?.InnerText;
- // create model
- return new ChucklefishMod
- {
- Name = name,
- Version = version,
- Url = url
- };
+ // return info
+ return page.SetInfo(name: name, version: version, url: url, downloads: Array.Empty<IModDownload>());
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
diff --git a/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishMod.cs b/src/SMAPI.Web/Framework/C