diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs
index c0d6408d..b71c8056 100644
--- a/src/SMAPI/Framework/IModMetadata.cs
+++ b/src/SMAPI/Framework/IModMetadata.cs
@@ -1,5 +1,6 @@
using StardewModdingAPI.Framework.ModData;
using StardewModdingAPI.Framework.ModLoading;
+using StardewModdingAPI.Framework.ModUpdateChecking;
namespace StardewModdingAPI.Framework
{
@@ -45,6 +46,11 @@ namespace StardewModdingAPI.Framework
/// Whether the mod is a content pack.
bool IsContentPack { get; }
+ /// The update status of this mod (if any).
+ ModUpdateStatus UpdateStatus { get; }
+
+ /// The preview update status of this mod (if any).
+ ModUpdateStatus PreviewUpdateStatus { get; }
/*********
** Public methods
@@ -72,6 +78,14 @@ namespace StardewModdingAPI.Framework
/// The mod-provided API.
IModMetadata SetApi(object api);
+ /// Set the update status.
+ /// The mod update status.
+ IModMetadata SetUpdateStatus(ModUpdateStatus updateStatus);
+
+ /// Set the preview update status.
+ /// The mod preview update status.
+ IModMetadata SetPreviewUpdateStatus(ModUpdateStatus previewUpdateStatus);
+
/// 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..88d2770c 100644
--- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs
+++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using StardewModdingAPI.Framework.ModData;
+using StardewModdingAPI.Framework.ModUpdateChecking;
namespace StardewModdingAPI.Framework.ModLoading
{
@@ -43,6 +44,12 @@ namespace StardewModdingAPI.Framework.ModLoading
/// The mod-provided API (if any).
public object Api { get; private set; }
+ /// The update status of this mod (if any).
+ public ModUpdateStatus UpdateStatus { get; private set; }
+
+ /// The preview update status of this mod (if any).
+ public ModUpdateStatus PreviewUpdateStatus { get; private set; }
+
/// Whether the mod is a content pack.
public bool IsContentPack => this.Manifest?.ContentPackFor != null;
@@ -115,6 +122,22 @@ namespace StardewModdingAPI.Framework.ModLoading
return this;
}
+ /// Set the update status.
+ /// The mod update status.
+ public IModMetadata SetUpdateStatus(ModUpdateStatus updateStatus)
+ {
+ this.UpdateStatus = updateStatus;
+ return this;
+ }
+
+ /// Set the preview update status.
+ /// The mod preview update status.
+ public IModMetadata SetPreviewUpdateStatus(ModUpdateStatus previewUpdateStatus)
+ {
+ this.PreviewUpdateStatus = previewUpdateStatus;
+ return this;
+ }
+
/// Whether the mod manifest was loaded (regardless of whether the mod itself was loaded).
public bool HasManifest()
{
diff --git a/src/SMAPI/Framework/ModUpdateChecking/ModUpdateStatus.cs b/src/SMAPI/Framework/ModUpdateChecking/ModUpdateStatus.cs
new file mode 100644
index 00000000..efb32aef
--- /dev/null
+++ b/src/SMAPI/Framework/ModUpdateChecking/ModUpdateStatus.cs
@@ -0,0 +1,37 @@
+namespace StardewModdingAPI.Framework.ModUpdateChecking
+{
+ /// Update status for a mod.
+ internal class ModUpdateStatus
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// The version that this mod can be updated to (if any).
+ public ISemanticVersion Version { get; }
+
+ /// The error checking for updates of this mod (if any).
+ public string Error { get; }
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The version that this mod can be update to.
+ public ModUpdateStatus(ISemanticVersion version)
+ {
+ this.Version = version;
+ }
+
+ /// Construct an instance.
+ /// The error checking for updates of this mod.
+ public ModUpdateStatus(string error)
+ {
+ this.Error = error;
+ }
+
+ /// Construct an instance.
+ public ModUpdateStatus()
+ {
+ }
+ }
+}
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index 8c6cea1e..2ee18a29 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -24,6 +24,7 @@ using StardewModdingAPI.Framework.ModData;
using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.ModHelpers;
using StardewModdingAPI.Framework.ModLoading;
+using StardewModdingAPI.Framework.ModUpdateChecking;
using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Serialisation;
@@ -662,7 +663,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,35 +672,65 @@ namespace StardewModdingAPI
// handle error
if (remoteInfo.Error != null)
{
+ if (mod.UpdateStatus?.Version == null)
+ mod.SetUpdateStatus(new ModUpdateStatus(remoteInfo.Error));
+ if (mod.PreviewUpdateStatus?.Version == null)
+ mod.SetUpdateStatus(new ModUpdateStatus(remoteInfo.Error));
+
this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {remoteInfo.Error}", LogLevel.Trace);
continue;
}
// 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))
+ bool validVersion = SemanticVersion.TryParse(mod.DataRecord?.GetRemoteVersionForUpdateChecks(remoteInfo.Version) ?? remoteInfo.Version, out ISemanticVersion remoteVersion);
+ bool validPreviewVersion = SemanticVersion.TryParse(remoteInfo.PreviewVersion, out ISemanticVersion remotePreviewVersion);
+
+ if (!validVersion && mod.UpdateStatus?.Version == null)
+ mod.SetUpdateStatus(new ModUpdateStatus($"Version is invalid: {remoteInfo.Version}"));
+ if (!validPreviewVersion && mod.PreviewUpdateStatus?.Version == null)
+ mod.SetPreviewUpdateStatus(new ModUpdateStatus($"Version is invalid: {remoteInfo.PreviewVersion}"));
+
+ if (!validVersion && !validPreviewVersion)
{
- this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: Mod has invalid version {remoteInfo.Version}", LogLevel.Trace);
+ this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: Mod has invalid versions. version: {remoteInfo.Version}, preview version: {remoteInfo.PreviewVersion}", 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}" : "okay")}.");
+ bool isPreviewUpdate = validPreviewVersion && localVersion.IsNewerThan(remoteVersion) && remotePreviewVersion.IsNewerThan(localVersion);
+ bool isUpdate = (validVersion && remoteVersion.IsNewerThan(localVersion)) || isPreviewUpdate;
+
+ this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {(isPreviewUpdate ? remoteInfo.PreviewVersion : remoteInfo.Version)}" : "okay")}.");
if (isUpdate)
{
- if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || remoteVersion.IsNewerThan(other.Version))
- updatesByMod[mod] = remoteInfo;
+ if (!updatesByMod.TryGetValue(mod, out Tuple other) || (isPreviewUpdate ? remotePreviewVersion : remoteVersion).IsNewerThan(other.Item2 ? other.Item1.PreviewVersion : other.Item1.Version))
+ {
+ updatesByMod[mod] = new Tuple(remoteInfo, isPreviewUpdate);
+
+ if (isPreviewUpdate)
+ mod.SetPreviewUpdateStatus(new ModUpdateStatus(remotePreviewVersion));
+ else
+ mod.SetUpdateStatus(new ModUpdateStatus(remoteVersion));
+ }
}
}
+ // set mods to have no updates
+ foreach (IModMetadata mod in results.Select(item => item.Mod)
+ .Where(item => !updatesByMod.ContainsKey(item)))
+ {
+ mod.SetUpdateStatus(new ModUpdateStatus());
+ mod.SetPreviewUpdateStatus(new ModUpdateStatus());
+ }
+
// output
if (updatesByMod.Any())
{
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);
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index fcd54c34..916dd053 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -110,6 +110,7 @@
+