add support for mapping non-semantic remote mod versions
This commit is contained in:
parent
90c8593ba9
commit
99023f9487
|
@ -6,6 +6,7 @@
|
|||
* Fixed some incompatible-mod errors not showing the mod URL.
|
||||
* Fixed rare crash with a specific combination of `manifest.json` fields and internal SMAPI mod data.
|
||||
* Fixed update checks not working for Nexus Mods due to a change in their API.
|
||||
* Fixed update checks failing for some older mods with non-standard versions.
|
||||
|
||||
* For the [log parser][]:
|
||||
* Fixed mod list not including all mods if at least one has no author name.
|
||||
|
|
|
@ -12,6 +12,9 @@ namespace StardewModdingAPI.Common.Models
|
|||
/// <summary>The namespaced mod keys to search.</summary>
|
||||
public string[] ModKeys { get; set; }
|
||||
|
||||
/// <summary>Whether to allow non-semantic versions, instead of returning an error for those.</summary>
|
||||
public bool AllowInvalidVersions { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -24,9 +27,11 @@ namespace StardewModdingAPI.Common.Models
|
|||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="modKeys">The namespaced mod keys to search.</param>
|
||||
public ModSearchModel(IEnumerable<string> modKeys)
|
||||
/// <param name="allowInvalidVersions">Whether to allow non-semantic versions, instead of returning an error for those.</param>
|
||||
public ModSearchModel(IEnumerable<string> modKeys, bool allowInvalidVersions)
|
||||
{
|
||||
this.ModKeys = modKeys.ToArray();
|
||||
this.AllowInvalidVersions = allowInvalidVersions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,14 +64,15 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
/// <summary>Fetch version metadata for the given mods.</summary>
|
||||
/// <param name="modKeys">The namespaced mod keys to search as a comma-delimited array.</param>
|
||||
/// <param name="allowInvalidVersions">Whether to allow non-semantic versions, instead of returning an error for those.</param>
|
||||
[HttpGet]
|
||||
public async Task<IDictionary<string, ModInfoModel>> GetAsync(string modKeys)
|
||||
public async Task<IDictionary<string, ModInfoModel>> GetAsync(string modKeys, bool allowInvalidVersions = false)
|
||||
{
|
||||
string[] modKeysArray = modKeys?.Split(',').ToArray();
|
||||
if (modKeysArray == null || !modKeysArray.Any())
|
||||
return new Dictionary<string, ModInfoModel>();
|
||||
|
||||
return await this.PostAsync(new ModSearchModel(modKeysArray));
|
||||
return await this.PostAsync(new ModSearchModel(modKeysArray, allowInvalidVersions));
|
||||
}
|
||||
|
||||
/// <summary>Fetch version metadata for the given mods.</summary>
|
||||
|
@ -79,7 +80,8 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
[HttpPost]
|
||||
public async Task<IDictionary<string, ModInfoModel>> PostAsync([FromBody] ModSearchModel search)
|
||||
{
|
||||
// sort & filter keys
|
||||
// parse model
|
||||
bool allowInvalidVersions = search?.AllowInvalidVersions ?? false;
|
||||
string[] modKeys = (search?.ModKeys?.ToArray() ?? new string[0])
|
||||
.Distinct(StringComparer.CurrentCultureIgnoreCase)
|
||||
.OrderBy(p => p, StringComparer.CurrentCultureIgnoreCase)
|
||||
|
@ -106,12 +108,20 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
// fetch mod info
|
||||
result[modKey] = await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{modID}".ToLower(), async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes);
|
||||
|
||||
// fetch info
|
||||
ModInfoModel info = await repository.GetModInfoAsync(modID);
|
||||
if (info.Error == null && (info.Version == null || !Regex.IsMatch(info.Version, this.VersionRegex, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)))
|
||||
info = new ModInfoModel(info.Name, info.Version, info.Url, info.Version == null ? "Mod has no version number." : $"Mod has invalid semantic version '{info.Version}'.");
|
||||
|
||||
// validate
|
||||
if (info.Error == null)
|
||||
{
|
||||
if (info.Version == null)
|
||||
info = new ModInfoModel(info.Name, info.Version, info.Url, "Mod has no version number.");
|
||||
if (!allowInvalidVersions && !Regex.IsMatch(info.Version, this.VersionRegex, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
|
||||
info = new ModInfoModel(info.Name, info.Version, info.Url, $"Mod has invalid semantic version '{info.Version}'.");
|
||||
}
|
||||
|
||||
// cache & return
|
||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes);
|
||||
return info;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -106,10 +106,10 @@ namespace StardewModdingAPI.Framework.ModData
|
|||
|
||||
/// <summary>Get a semantic local version for update checks.</summary>
|
||||
/// <param name="version">The remote version to normalise.</param>
|
||||
public string GetLocalVersionForUpdateChecks(string version)
|
||||
public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
|
||||
{
|
||||
return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version, out string newVersion)
|
||||
? newVersion
|
||||
return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion)
|
||||
? new SemanticVersion(newVersion)
|
||||
: version;
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,11 @@ namespace StardewModdingAPI.Framework.ModData
|
|||
/// <param name="version">The remote version to normalise.</param>
|
||||
public string GetRemoteVersionForUpdateChecks(string version)
|
||||
{
|
||||
// normalise version if possible
|
||||
if (SemanticVersion.TryParse(version, out ISemanticVersion parsed))
|
||||
version = parsed.ToString();
|
||||
|
||||
// fetch remote version
|
||||
return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion)
|
||||
? newVersion
|
||||
: version;
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModData
|
|||
*********/
|
||||
/// <summary>Get a semantic local version for update checks.</summary>
|
||||
/// <param name="version">The remote version to normalise.</param>
|
||||
public string GetLocalVersionForUpdateChecks(string version)
|
||||
public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
|
||||
{
|
||||
return this.DataRecord.GetLocalVersionForUpdateChecks(version);
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
return this.Post<ModSearchModel, Dictionary<string, ModInfoModel>>(
|
||||
$"v{this.Version}/mods",
|
||||
new ModSearchModel(modKeys)
|
||||
new ModSearchModel(modKeys, allowInvalidVersions: true)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -604,29 +604,30 @@ namespace StardewModdingAPI
|
|||
foreach (var result in results)
|
||||
{
|
||||
IModMetadata mod = result.Mod;
|
||||
ModInfoModel info = result.Info;
|
||||
ModInfoModel remoteInfo = result.Info;
|
||||
|
||||
// handle error
|
||||
if (info.Error != null)
|
||||
if (remoteInfo.Error != null)
|
||||
{
|
||||
this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {info.Error}", LogLevel.Trace);
|
||||
this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {remoteInfo.Error}", LogLevel.Trace);
|
||||
continue;
|
||||
}
|
||||
|
||||
// track update
|
||||
ISemanticVersion localVersion = mod.DataRecord != null
|
||||
? new SemanticVersion(mod.DataRecord.GetLocalVersionForUpdateChecks(mod.Manifest.Version.ToString()))
|
||||
: mod.Manifest.Version;
|
||||
ISemanticVersion latestVersion = new SemanticVersion(mod.DataRecord != null
|
||||
? mod.DataRecord.GetRemoteVersionForUpdateChecks(new SemanticVersion(info.Version).ToString())
|
||||
: info.Version
|
||||
);
|
||||
bool isUpdate = latestVersion.IsNewerThan(localVersion);
|
||||
this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {info.Version}{(!latestVersion.Equals(new SemanticVersion(info.Version)) ? $" [{latestVersion}]" : "")}" : "okay")}.");
|
||||
// normalise versions
|
||||
ISemanticVersion localVersion = mod.DataRecord?.GetLocalVersionForUpdateChecks(mod.Manifest.Version) ?? mod.Manifest.Version;
|
||||
if (!SemanticVersion.TryParse(mod.DataRecord?.GetRemoteVersionForUpdateChecks(remoteInfo.Version) ?? remoteInfo.Version, out ISemanticVersion remoteVersion))
|
||||
{
|
||||
this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: Mod has invalid version {remoteInfo.Version}", LogLevel.Trace);
|
||||
continue;
|
||||
}
|
||||
|
||||
// compare versions
|
||||
bool isUpdate = remoteVersion.IsNewerThan(localVersion);
|
||||
this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {remoteInfo.Version}{(!remoteVersion.Equals(new SemanticVersion(remoteInfo.Version)) ? $" [{remoteVersion}]" : "")}" : "okay")}.");
|
||||
if (isUpdate)
|
||||
{
|
||||
if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || latestVersion.IsNewerThan(other.Version))
|
||||
updatesByMod[mod] = info;
|
||||
if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || remoteVersion.IsNewerThan(other.Version))
|
||||
updatesByMod[mod] = remoteInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue