diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-09-23 19:15:07 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-09-23 19:15:07 -0400 |
commit | 873abef23563f5273e9b66d7b7e3cc2f5e4e0e92 (patch) | |
tree | 719f112605271149b2e75a1bd1d44a8b870e9fdc /src/StardewModdingAPI | |
parent | e178ed14be24e3971d50addaebb3d13c19c18304 (diff) | |
download | SMAPI-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.cs | 10 | ||||
-rw-r--r-- | src/StardewModdingAPI/Framework/Models/Manifest.cs | 10 | ||||
-rw-r--r-- | src/StardewModdingAPI/Framework/SGame.cs | 2 | ||||
-rw-r--r-- | src/StardewModdingAPI/IManifest.cs | 10 | ||||
-rw-r--r-- | src/StardewModdingAPI/Program.cs | 107 | ||||
-rw-r--r-- | src/StardewModdingAPI/StardewModdingAPI.config.json | 6 |
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, |