summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs51
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs48
-rw-r--r--src/SMAPI.Web/Framework/ModSiteManager.cs28
-rw-r--r--src/SMAPI.sln.DotSettings1
4 files changed, 91 insertions, 37 deletions
diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
index f6044148..7e4d0220 100644
--- a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
+++ b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
@@ -17,6 +17,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <summary>The mod ID within the repository.</summary>
public string ID { get; }
+ /// <summary>If specified, a substring in download names/descriptions to match.</summary>
+ public string Subkey { get; }
+
/// <summary>Whether the update key seems to be valid.</summary>
public bool LooksValid { get; }
@@ -28,11 +31,13 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <param name="rawText">The raw update key text.</param>
/// <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)
+ /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param>
+ public UpdateKey(string rawText, ModSiteKey site, string id, string subkey)
{
this.RawText = rawText?.Trim();
this.Site = site;
this.ID = id?.Trim();
+ this.Subkey = subkey?.Trim();
this.LooksValid =
site != ModSiteKey.Unknown
&& !string.IsNullOrWhiteSpace(id);
@@ -41,8 +46,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <summary>Construct an instance.</summary>
/// <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) { }
+ /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param>
+ public UpdateKey(ModSiteKey site, string id, string subkey)
+ : this(UpdateKey.GetString(site, id, subkey), site, id, subkey) { }
/// <summary>Parse a raw update key.</summary>
/// <param name="raw">The raw update key to parse.</param>
@@ -54,7 +60,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
{
string[] parts = raw?.Trim().Split(':');
if (parts == null || parts.Length != 2)
- return new UpdateKey(raw, ModSiteKey.Unknown, null);
+ return new UpdateKey(raw, ModSiteKey.Unknown, null, null);
rawSite = parts[0].Trim();
id = parts[1].Trim();
@@ -62,20 +68,32 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
if (string.IsNullOrWhiteSpace(id))
id = null;
+ // extract subkey
+ string subkey = null;
+ if (id != null)
+ {
+ string[] parts = id.Split('@');
+ if (parts.Length == 2)
+ {
+ id = parts[0].Trim();
+ subkey = $"@{parts[1]}".Trim();
+ }
+ }
+
// parse
if (!Enum.TryParse(rawSite, true, out ModSiteKey site))
- return new UpdateKey(raw, ModSiteKey.Unknown, id);
+ return new UpdateKey(raw, ModSiteKey.Unknown, id, subkey);
if (id == null)
- return new UpdateKey(raw, site, null);
+ return new UpdateKey(raw, site, null, subkey);
- return new UpdateKey(raw, site, id);
+ return new UpdateKey(raw, site, id, subkey);
}
/// <summary>Get a string that represents the current object.</summary>
public override string ToString()
{
return this.LooksValid
- ? UpdateKey.GetString(this.Site, this.ID)
+ ? UpdateKey.GetString(this.Site, this.ID, this.Subkey)
: this.RawText;
}
@@ -83,10 +101,18 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <param name="other">An object to compare with this object.</param>
public bool Equals(UpdateKey other)
{
+ if (!this.LooksValid)
+ {
+ return
+ other?.LooksValid == false
+ && this.RawText.Equals(other.RawText, StringComparison.OrdinalIgnoreCase);
+ }
+
return
other != null
&& this.Site == other.Site
- && string.Equals(this.ID, other.ID, StringComparison.InvariantCultureIgnoreCase);
+ && string.Equals(this.ID, other.ID, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(this.Subkey, other.Subkey, StringComparison.OrdinalIgnoreCase);
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
@@ -100,15 +126,16 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <returns>A hash code for the current object.</returns>
public override int GetHashCode()
{
- return $"{this.Site}:{this.ID}".ToLower().GetHashCode();
+ return this.ToString().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)
+ /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param>
+ public static string GetString(ModSiteKey site, string id, string subkey = null)
{
- return $"{site}:{id}".Trim();
+ return $"{site}:{id}{subkey}".Trim();
}
}
}
diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs
index 14be520d..028fc613 100644
--- a/src/SMAPI.Web/Controllers/ModsApiController.cs
+++ b/src/SMAPI.Web/Controllers/ModsApiController.cs
@@ -135,7 +135,7 @@ namespace StardewModdingAPI.Web.Controllers
// validate update key
if (!updateKey.LooksValid)
{
- errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541'.");
+ errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541', with an optional subkey like 'Nexus:541@subkey'.");
continue;
}
@@ -271,7 +271,7 @@ namespace StardewModdingAPI.Web.Controllers
}
// get version info
- return this.ModSites.GetPageVersions(page, allowNonStandardVersions, mapRemoteVersions);
+ return this.ModSites.GetPageVersions(page, updateKey.Subkey, allowNonStandardVersions, mapRemoteVersions);
}
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary>
@@ -280,44 +280,56 @@ namespace StardewModdingAPI.Web.Controllers
/// <param name="entry">The mod's entry in the wiki list.</param>
private IEnumerable<UpdateKey> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry)
{
+ // get every update key (including duplicates)
IEnumerable<string> GetRaw()
{
// specified update keys
if (specifiedKeys != null)
{
foreach (string key in specifiedKeys)
- yield return key?.Trim();
+ {
+ if (!string.IsNullOrWhiteSpace(key))
+ yield return key.Trim();
+ }
}
// default update key
string defaultKey = record?.GetDefaultUpdateKey();
- if (defaultKey != null)
+ if (!string.IsNullOrWhiteSpace(defaultKey))
yield return defaultKey;
// wiki metadata
if (entry != null)
{
if (entry.NexusID.HasValue)
- yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID?.ToString());
+ yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID.ToString());
if (entry.ModDropID.HasValue)
- yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID?.ToString());
+ yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID.ToString());
if (entry.CurseForgeID.HasValue)
- yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID?.ToString());
+ yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID.ToString());
if (entry.ChucklefishID.HasValue)
- yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID?.ToString());
+ yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID.ToString());
}
}
- HashSet<UpdateKey> seen = new HashSet<UpdateKey>();
- foreach (string rawKey in GetRaw())
- {
- if (string.IsNullOrWhiteSpace(rawKey))
- continue;
-
- UpdateKey key = UpdateKey.Parse(rawKey);
- if (seen.Add(key))
- yield return key;
- }
+ // get unique update keys
+ var subkeyRoots = new HashSet<UpdateKey>();
+ List<UpdateKey> updateKeys = GetRaw()
+ .Select(raw =>
+ {
+ var key = UpdateKey.Parse(raw);
+ if (key.Subkey != null)
+ subkeyRoots.Add(new UpdateKey(key.Site, key.ID, null));
+ return key;
+ })
+ .Distinct()
+ .ToList();
+
+ // if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority
+ if (subkeyRoots.Any())
+ updateKeys.RemoveAll(subkeyRoots.Contains);
+
+ return updateKeys;
}
}
}
diff --git a/src/SMAPI.Web/Framework/ModSiteManager.cs b/src/SMAPI.Web/Framework/ModSiteManager.cs
index eaae7935..68b4c6ac 100644
--- a/src/SMAPI.Web/Framework/ModSiteManager.cs
+++ b/src/SMAPI.Web/Framework/ModSiteManager.cs
@@ -54,9 +54,10 @@ namespace StardewModdingAPI.Web.Framework
/// <summary>Parse version info for the given mod page info.</summary>
/// <param name="page">The mod page info.</param>
+ /// <param name="subkey">The optional update subkey to match in available files. (If no file names or descriptions contain the subkey, it'll be ignored.)</param>
/// <param name="mapRemoteVersions">Maps remote versions to a semantic version for update checks.</param>
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
- public ModInfoModel GetPageVersions(IModPage page, bool allowNonStandardVersions, IDictionary<string, string> mapRemoteVersions)
+ public ModInfoModel GetPageVersions(IModPage page, string subkey, bool allowNonStandardVersions, IDictionary<string, string> mapRemoteVersions)
{
// get base model
ModInfoModel model = new ModInfoModel()
@@ -66,7 +67,10 @@ namespace StardewModdingAPI.Web.Framework
return model;
// fetch versions
- if (!this.TryGetLatestVersions(page, allowNonStandardVersions, mapRemoteVersions, out ISemanticVersion mainVersion, out ISemanticVersion previewVersion))
+ bool hasVersions = this.TryGetLatestVersions(page, subkey, allowNonStandardVersions, mapRemoteVersions, out ISemanticVersion mainVersion, out ISemanticVersion previewVersion);
+ if (!hasVersions && subkey != null)
+ hasVersions = this.TryGetLatestVersions(page, null, allowNonStandardVersions, mapRemoteVersions, out mainVersion, out previewVersion);
+ if (!hasVersions)
return model.SetError(RemoteModStatus.InvalidData, $"The {page.Site} mod with ID '{page.Id}' has no valid versions.");
// return info
@@ -96,11 +100,12 @@ namespace StardewModdingAPI.Web.Framework
*********/
/// <summary>Get the mod version numbers for the given mod.</summary>
/// <param name="mod">The mod to check.</param>
+ /// <param name="subkey">The optional update subkey to match in available files. (If no file names or descriptions contain the subkey, it'll be ignored.)</param>
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
/// <param name="mapRemoteVersions">Maps remote versions to a semantic version for update checks.</param>
/// <param name="main">The main mod version.</param>
/// <param name="preview">The latest prerelease version, if newer than <paramref name="main"/>.</param>
- private bool TryGetLatestVersions(IModPage mod, bool allowNonStandardVersions, IDictionary<string, string> mapRemoteVersions, out ISemanticVersion main, out ISemanticVersion preview)
+ private bool TryGetLatestVersions(IModPage mod, string subkey, bool allowNonStandardVersions, IDictionary<string, string> mapRemoteVersions, out ISemanticVersion main, out ISemanticVersion preview)
{
main = null;
preview = null;
@@ -113,14 +118,23 @@ namespace StardewModdingAPI.Web.Framework
if (mod != null)
{
- // get versions
- main = ParseVersion(mod.Version);
- foreach (string rawVersion in mod.Downloads.Select(p => p.Version))
+ // get mod version
+ if (subkey == null)
+ main = ParseVersion(mod.Version);
+
+ // get file versions
+ foreach (IModDownload download in mod.Downloads)
{
- ISemanticVersion cur = ParseVersion(rawVersion);
+ // check for subkey if specified
+ if (subkey != null && download.Name?.Contains(subkey, StringComparison.OrdinalIgnoreCase) != true && download.Description?.Contains(subkey, StringComparison.OrdinalIgnoreCase) != true)
+ continue;
+
+ // parse version
+ ISemanticVersion cur = ParseVersion(download.Version);
if (cur == null)
continue;
+ // track highest versions
if (main == null || cur.IsNewerThan(main))
main = cur;
if (cur.IsPrerelease() && (preview == null || cur.IsNewerThan(preview)))
diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings
index 556f1ec0..05caa938 100644
--- a/src/SMAPI.sln.DotSettings
+++ b/src/SMAPI.sln.DotSettings
@@ -58,6 +58,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=stackable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stardew/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=subdomain/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=subkey/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=synchronised/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=textbox/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbstick/@EntryIndexedValue">True</s:Boolean>