diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index c0d6408d..673c5b2e 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -45,6 +45,14 @@ namespace StardewModdingAPI.Framework /// Whether the mod is a content pack. bool IsContentPack { get; } + /// The latest version of the mod. + ISemanticVersion LatestVersion { get; } + + /// The latest preview version of the mod, if any. + ISemanticVersion LatestPreviewVersion { get; } + + /// The error checking for updates for this mod, if any. + string UpdateCheckError { get; } /********* ** Public methods @@ -72,6 +80,15 @@ namespace StardewModdingAPI.Framework /// The mod-provided API. IModMetadata SetApi(object api); + /// Set the update status, indicating no errors happened. + /// The latest version. + /// The latest preview version. + IModMetadata SetUpdateStatus(ISemanticVersion latestVersion, ISemanticVersion latestPreviewVersion); + + /// Set the update status, indicating an error happened. + /// The error checking for updates, if any. + IModMetadata SetUpdateStatus(string updateCheckError); + /// Whether the mod manifest was loaded (regardless of whether the mod itself was loaded). bool HasManifest(); diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index e4ec7e3b..8f544da3 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -43,6 +43,15 @@ namespace StardewModdingAPI.Framework.ModLoading /// The mod-provided API (if any). public object Api { get; private set; } + /// The latest version of the mod. + public ISemanticVersion LatestVersion { get; private set; } + + /// The latest preview version of the mod, if any. + public ISemanticVersion LatestPreviewVersion { get; private set; } + + /// The error checking for updates for this mod, if any. + public string UpdateCheckError { get; private set; } + /// Whether the mod is a content pack. public bool IsContentPack => this.Manifest?.ContentPackFor != null; @@ -115,6 +124,24 @@ namespace StardewModdingAPI.Framework.ModLoading return this; } + /// Set the update status. + /// The latest version. + /// The latest preview version. + public IModMetadata SetUpdateStatus(ISemanticVersion latestVersion, ISemanticVersion latestPreviewVersion) + { + this.LatestVersion = latestVersion; + this.LatestPreviewVersion = latestPreviewVersion; + return this; + } + + // Set the update status, indicating an error happened. + /// The error checking for updates, if any. + public IModMetadata SetUpdateStatus(string updateCheckError) + { + this.UpdateCheckError = updateCheckError; + return this; + } + /// Whether the mod manifest was loaded (regardless of whether the mod itself was loaded). public bool HasManifest() { diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 8c6cea1e..dc034331 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -662,7 +662,7 @@ namespace StardewModdingAPI .ToArray(); // extract latest versions - IDictionary updatesByMod = new Dictionary(); + IDictionary> updatesByMod = new Dictionary>(); foreach (var result in results) { IModMetadata mod = result.Mod; @@ -671,6 +671,7 @@ namespace StardewModdingAPI // handle error if (remoteInfo.Error != null) { + mod.SetUpdateStatus(remoteInfo.Error); this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {remoteInfo.Error}", LogLevel.Trace); continue; } @@ -679,17 +680,25 @@ namespace StardewModdingAPI 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); + string errorInfo = $"Mod has invalid version {remoteInfo.Version}"; + + mod.SetUpdateStatus(errorInfo); + this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {errorInfo}", LogLevel.Trace); continue; } + SemanticVersion.TryParse(remoteInfo.PreviewVersion, out ISemanticVersion remotePreviewVersion); + mod.SetUpdateStatus(remoteVersion, remotePreviewVersion); + // 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}" : "okay")}."); + bool isNonPreviewUpdate = remoteVersion.IsNewerThan(localVersion); + bool isUpdate = isNonPreviewUpdate || + (localVersion.IsNewerThan(remoteVersion) && remotePreviewVersion.IsNewerThan(localVersion)); + this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {(isNonPreviewUpdate ? remoteInfo.Version : remoteInfo.PreviewVersion)}" : "okay")}."); if (isUpdate) { - if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || remoteVersion.IsNewerThan(other.Version)) - updatesByMod[mod] = remoteInfo; + if (!updatesByMod.TryGetValue(mod, out Tuple other) || (isNonPreviewUpdate ? remoteVersion : remotePreviewVersion).IsNewerThan(other.Item2 ? other.Item1.PreviewVersion : other.Item1.Version)) + updatesByMod[mod] = new Tuple(remoteInfo, !isNonPreviewUpdate); } } @@ -699,7 +708,7 @@ namespace StardewModdingAPI this.Monitor.Newline(); this.Monitor.Log($"You can update {updatesByMod.Count} mod{(updatesByMod.Count != 1 ? "s" : "")}:", LogLevel.Alert); foreach (var entry in updatesByMod.OrderBy(p => p.Key.DisplayName)) - this.Monitor.Log($" {entry.Key.DisplayName} {entry.Value.Version}: {entry.Value.Url}", LogLevel.Alert); + this.Monitor.Log($" {entry.Key.DisplayName} {(entry.Value.Item2 ? entry.Value.Item1.PreviewVersion : entry.Value.Item1.Version)}: {entry.Value.Item1.Url}", LogLevel.Alert); } else this.Monitor.Log(" All mods up to date.", LogLevel.Trace);