summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-09-23 19:15:07 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-09-23 19:15:07 -0400
commit873abef23563f5273e9b66d7b7e3cc2f5e4e0e92 (patch)
tree719f112605271149b2e75a1bd1d44a8b870e9fdc /src/StardewModdingAPI
parente178ed14be24e3971d50addaebb3d13c19c18304 (diff)
downloadSMAPI-873abef23563f5273e9b66d7b7e3cc2f5e4e0e92.tar.gz
SMAPI-873abef23563f5273e9b66d7b7e3cc2f5e4e0e92.tar.bz2
SMAPI-873abef23563f5273e9b66d7b7e3cc2f5e4e0e92.zip
add mod update checks based on manifest fields (#336)
Diffstat (limited to 'src/StardewModdingAPI')
-rw-r--r--src/StardewModdingAPI/Events/GameEvents.cs10
-rw-r--r--src/StardewModdingAPI/Framework/Models/Manifest.cs10
-rw-r--r--src/StardewModdingAPI/Framework/SGame.cs2
-rw-r--r--src/StardewModdingAPI/IManifest.cs10
-rw-r--r--src/StardewModdingAPI/Program.cs107
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.config.json6
6 files changed, 119 insertions, 26 deletions
diff --git a/src/StardewModdingAPI/Events/GameEvents.cs b/src/StardewModdingAPI/Events/GameEvents.cs
index 5610e67a..deb71a86 100644
--- a/src/StardewModdingAPI/Events/GameEvents.cs
+++ b/src/StardewModdingAPI/Events/GameEvents.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Framework;
@@ -39,9 +39,6 @@ namespace StardewModdingAPI.Events
/// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
internal static event EventHandler InitializeInternal;
- /// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary>
- internal static event EventHandler GameLoadedInternal;
-
#if SMAPI_1_x
/// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
[Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.Initialize) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")]
@@ -143,19 +140,14 @@ namespace StardewModdingAPI.Events
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}", GameEvents._LoadContent?.GetInvocationList());
}
-#endif
/// <summary>Raise a <see cref="GameLoadedInternal"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeGameLoaded(IMonitor monitor)
{
- monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoadedInternal)}", GameEvents.GameLoadedInternal?.GetInvocationList());
-#if SMAPI_1_x
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents._GameLoaded?.GetInvocationList());
-#endif
}
-#if SMAPI_1_x
/// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeFirstUpdateTick(IMonitor monitor)
diff --git a/src/StardewModdingAPI/Framework/Models/Manifest.cs b/src/StardewModdingAPI/Framework/Models/Manifest.cs
index 29c3517e..f97cb8ff 100644
--- a/src/StardewModdingAPI/Framework/Models/Manifest.cs
+++ b/src/StardewModdingAPI/Framework/Models/Manifest.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using StardewModdingAPI.Framework.Serialisation;
@@ -35,6 +35,14 @@ namespace StardewModdingAPI.Framework.Models
[JsonConverter(typeof(SFieldConverter))]
public IManifestDependency[] Dependencies { get; set; }
+#if !SMAPI_1_x
+ /// <summary>The mod's unique ID in Nexus Mods (if any), used for update checks.</summary>
+ public string NexusID { get; set; }
+
+ /// <summary>The mod's organisation and project name on GitHub (if any), used for update checks.</summary>
+ public string GitHubProject { get; set; }
+#endif
+
/// <summary>The unique mod ID.</summary>
public string UniqueID { get; set; }
diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs
index 76c106d7..387aeacc 100644
--- a/src/StardewModdingAPI/Framework/SGame.cs
+++ b/src/StardewModdingAPI/Framework/SGame.cs
@@ -297,8 +297,8 @@ namespace StardewModdingAPI.Framework
GameEvents.InvokeInitialize(this.Monitor);
#if SMAPI_1_x
GameEvents.InvokeLoadContent(this.Monitor);
-#endif
GameEvents.InvokeGameLoaded(this.Monitor);
+#endif
}
/*********
diff --git a/src/StardewModdingAPI/IManifest.cs b/src/StardewModdingAPI/IManifest.cs
index 407db1ce..28f6570c 100644
--- a/src/StardewModdingAPI/IManifest.cs
+++ b/src/StardewModdingAPI/IManifest.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace StardewModdingAPI
{
@@ -32,7 +32,13 @@ namespace StardewModdingAPI
/// <summary>The other mods that must be loaded before this mod.</summary>
IManifestDependency[] Dependencies { get; }
+ /// <summary>The mod's unique ID in Nexus Mods (if any), used for update checks.</summary>
+ string NexusID { get; set; }
+
+ /// <summary>The mod's organisation and project name on GitHub (if any), used for update checks.</summary>
+ string GitHubProject { get; set; }
+
/// <summary>Any manifest fields which didn't match a valid field.</summary>
IDictionary<string, object> ExtraFields { get; }
}
-} \ No newline at end of file
+}
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index df94b02a..cee3aefd 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -188,7 +188,6 @@ namespace StardewModdingAPI
#endif
this.GameInstance.Exiting += (sender, e) => this.Dispose();
GameEvents.InitializeInternal += (sender, e) => this.InitialiseAfterGameStart();
- GameEvents.GameLoadedInternal += (sender, e) => this.CheckForUpdateAsync();
ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged();
// set window titles
@@ -437,6 +436,9 @@ namespace StardewModdingAPI
#else
this.LoadMods(mods, new JsonHelper(), this.ContentManager);
#endif
+
+ // check for updates
+ this.CheckForUpdatesAsync(mods);
}
if (this.Monitor.IsExiting)
{
@@ -563,28 +565,113 @@ namespace StardewModdingAPI
return !issuesFound;
}
- /// <summary>Asynchronously check for a new version of SMAPI, and print a message to the console if an update is available.</summary>
- private void CheckForUpdateAsync()
+ /// <summary>Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available.</summary>
+ /// <param name="mods">The mods to include in the update check (if eligible).</param>
+ private void CheckForUpdatesAsync(IModMetadata[] mods)
{
if (!this.Settings.CheckForUpdates)
return;
new Thread(() =>
{
+ // update info
+ List<string> updates = new List<string>();
+ bool smapiUpdate = false;
+ int modUpdates = 0;
+
+ // create client
+ WebApiClient client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion);
+
+ // fetch SMAPI version
try
{
- var client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion);
- string key = $"GitHub:{this.Settings.GitHubProjectName}";
- ModInfoModel info = client.GetModInfoAsync(key).Result[key];
- if (info.Error != null)
- this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{info.Error}");
- else if (new SemanticVersion(info.Version).IsNewerThan(Constants.ApiVersion))
- this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {info.Version}.", LogLevel.Alert);
+ ModInfoModel response = client.GetModInfoAsync($"GitHub:{this.Settings.GitHubProjectName}").Result.Single().Value;
+ if (response.Error != null)
+ this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{response.Error}", LogLevel.Warn);
+ else if (new SemanticVersion(response.Version).IsNewerThan(Constants.ApiVersion))
+ {
+ smapiUpdate = true;
+ updates.Add($"SMAPI {response.Version}: {response.Url}");
+ }
}
catch (Exception ex)
{
this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{ex.GetLogSummary()}");
}
+
+ // fetch mod versions
+#if !SMAPI_1_x
+ try
+ {
+ // prepare update-check data
+ IDictionary<string, IModMetadata> modsByKey = new Dictionary<string, IModMetadata>(StringComparer.InvariantCultureIgnoreCase);
+ foreach (IModMetadata mod in mods)
+ {
+ if (!string.IsNullOrWhiteSpace(mod.Manifest.NexusID))
+ modsByKey[$"Nexus:{mod.Manifest.NexusID}"] = mod;
+ if (!string.IsNullOrWhiteSpace(mod.Manifest.GitHubProject))
+ modsByKey[$"GitHub:{mod.Manifest.GitHubProject}"] = mod;
+ }
+
+ // fetch results
+ IDictionary<string, ModInfoModel> response = client.GetModInfoAsync(modsByKey.Keys.ToArray()).Result;
+ IDictionary<IModMetadata, ModInfoModel> updatesByMod = new Dictionary<IModMetadata, ModInfoModel>();
+ foreach (var entry in response)
+ {
+ // handle error
+ if (entry.Value.Error != null)
+ {
+ this.Monitor.Log($"Couldn't fetch version of {modsByKey[entry.Key].DisplayName} with key {entry.Key}:\n{entry.Value.Error}", LogLevel.Trace);
+ continue;
+ }
+
+ // collect latest mod version
+ IModMetadata mod = modsByKey[entry.Key];
+ ISemanticVersion version = new SemanticVersion(entry.Value.Version);
+ if (version.IsNewerThan(mod.Manifest.Version))
+ {
+ if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || version.IsNewerThan(other.Version))
+ {
+ updatesByMod[mod] = entry.Value;
+ modUpdates++;
+ }
+ }
+ }
+
+ // add to output queue
+ if (updatesByMod.Any())
+ {
+ foreach (var entry in updatesByMod.OrderBy(p => p.Key.DisplayName))
+ updates.Add($"{entry.Key.DisplayName} {entry.Value.Version}: {entry.Value.Url}");
+ }
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"Couldn't check for new mod versions:\n{ex.GetLogSummary()}", LogLevel.Trace);
+ }
+#endif
+
+ // output
+ if (updates.Any())
+ {
+#if !SMAPI_1_x
+ this.Monitor.Newline();
+#endif
+
+ // print intro
+ string intro = "";
+ if (smapiUpdate)
+ intro = "You can update SMAPI";
+ if (modUpdates > 0)
+ intro += $"{(smapiUpdate ? " and" : "You can update")} {modUpdates} mod{(modUpdates != 1 ? "s" : "")}";
+ intro += ":";
+ this.Monitor.Log(intro, LogLevel.Alert);
+
+ // print update list
+ foreach (string line in updates)
+ this.Monitor.Log($" {line}", LogLevel.Alert);
+ }
+
}).Start();
}
diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json
index 67d8f270..c91d169c 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.config.json
+++ b/src/StardewModdingAPI/StardewModdingAPI.config.json
@@ -15,9 +15,9 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"DeveloperMode": true,
/**
- * Whether SMAPI should check for a newer version when you load the game. If a new version is
- * available, a small message will appear in the console. This doesn't affect the load time even
- * if your connection is offline or slow, because it happens in the background.
+ * Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new
+ * versions are available, an alert will be shown in the console. This doesn't affect the load
+ * time even if your connection is offline or slow, because it happens in the background.
*/
"CheckForUpdates": true,