migrate update checks to FluentHttpClient
WebClient isn't needed for compatibility with macOS after the .NET 5 update in Stardew Valley 1.5.5, and causes noticeable lag for some players even when running on a background thread.
This commit is contained in:
parent
5e1212e99a
commit
6d11c41fac
|
@ -35,6 +35,10 @@ This assumes `find-game-folder.targets` has already been imported and validated.
|
||||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Pdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.Pdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
<Copy SourceFiles="$(TargetDir)\MonoMod.Common.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
<Copy SourceFiles="$(TargetDir)\MonoMod.Common.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
|
||||||
|
<!-- FluentHttpClient + dependencies -->
|
||||||
|
<Copy SourceFiles="$(TargetDir)\Pathoschild.Http.Client.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\System.Net.Http.Formatting.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
|
||||||
<!-- .NET dependencies -->
|
<!-- .NET dependencies -->
|
||||||
<Copy SourceFiles="$(TargetDir)\System.Management.dll" DestinationFolder="$(GamePath)\smapi-internal" Condition="$(OS) == 'Windows_NT'" />
|
<Copy SourceFiles="$(TargetDir)\System.Management.dll" DestinationFolder="$(GamePath)\smapi-internal" Condition="$(OS) == 'Windows_NT'" />
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ for folder in ${folders[@]}; do
|
||||||
cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal"
|
cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal"
|
||||||
|
|
||||||
# bundle smapi-internal
|
# bundle smapi-internal
|
||||||
for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml"; do
|
for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
|
||||||
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
|
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,7 @@ foreach ($folder in $folders) {
|
||||||
cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
|
cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
|
||||||
|
|
||||||
# bundle smapi-internal
|
# bundle smapi-internal
|
||||||
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml")) {
|
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
|
||||||
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
|
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,12 @@
|
||||||
|
|
||||||
## Upcoming release
|
## Upcoming release
|
||||||
* For players:
|
* For players:
|
||||||
* Minor optimizations.
|
* Fixed lag which occurred for some players since Stardew Valley 1.5.5.
|
||||||
* Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0.
|
* Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0.
|
||||||
|
|
||||||
|
* For mod authors:
|
||||||
|
* The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI.
|
||||||
|
|
||||||
* For the web UI:
|
* For the web UI:
|
||||||
* Updated the JSON validator/schema for Content Patcher 1.27.0.
|
* Updated the JSON validator/schema for Content Patcher 1.27.0.
|
||||||
* Fixed the mod count in the log parser metadata.
|
* Fixed the mod count in the log parser metadata.
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Pathoschild.Http.Client;
|
||||||
using StardewModdingAPI.Toolkit.Serialization;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
using StardewModdingAPI.Toolkit.Utilities;
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
{
|
{
|
||||||
/// <summary>Provides methods for interacting with the SMAPI web API.</summary>
|
/// <summary>Provides methods for interacting with the SMAPI web API.</summary>
|
||||||
public class WebApiClient
|
public class WebApiClient : IDisposable
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Fields
|
** Fields
|
||||||
*********/
|
*********/
|
||||||
/// <summary>The base URL for the web API.</summary>
|
|
||||||
private readonly Uri BaseUrl;
|
|
||||||
|
|
||||||
/// <summary>The API version number.</summary>
|
/// <summary>The API version number.</summary>
|
||||||
private readonly ISemanticVersion Version;
|
private readonly ISemanticVersion Version;
|
||||||
|
|
||||||
/// <summary>The JSON serializer settings to use.</summary>
|
/// <summary>The underlying HTTP client.</summary>
|
||||||
private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings;
|
private readonly IClient Client;
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
@ -32,8 +29,11 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <param name="version">The web API version.</param>
|
/// <param name="version">The web API version.</param>
|
||||||
public WebApiClient(string baseUrl, ISemanticVersion version)
|
public WebApiClient(string baseUrl, ISemanticVersion version)
|
||||||
{
|
{
|
||||||
this.BaseUrl = new Uri(baseUrl);
|
|
||||||
this.Version = version;
|
this.Version = version;
|
||||||
|
this.Client = new FluentClient(baseUrl)
|
||||||
|
.SetUserAgent($"SMAPI/{version}");
|
||||||
|
|
||||||
|
this.Client.Formatters.JsonFormatter.SerializerSettings = JsonHelper.CreateDefaultSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get metadata about a set of mods from the web API.</summary>
|
/// <summary>Get metadata about a set of mods from the web API.</summary>
|
||||||
|
@ -42,36 +42,22 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <param name="gameVersion">The Stardew Valley version installed by the player.</param>
|
/// <param name="gameVersion">The Stardew Valley version installed by the player.</param>
|
||||||
/// <param name="platform">The OS on which the player plays.</param>
|
/// <param name="platform">The OS on which the player plays.</param>
|
||||||
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
||||||
public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
|
public async Task<IDictionary<string, ModEntryModel>> GetModInfoAsync(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
|
||||||
{
|
{
|
||||||
return this.Post<ModSearchModel, ModEntryModel[]>(
|
ModEntryModel[] result = await this.Client
|
||||||
$"v{this.Version}/mods",
|
.PostAsync(
|
||||||
new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
|
$"v{this.Version}/mods",
|
||||||
).ToDictionary(p => p.ID);
|
new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
|
||||||
|
)
|
||||||
|
.As<ModEntryModel[]>();
|
||||||
|
|
||||||
|
return result.ToDictionary(p => p.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
/*********
|
public void Dispose()
|
||||||
** Private methods
|
|
||||||
*********/
|
|
||||||
/// <summary>Fetch the response from the backend API.</summary>
|
|
||||||
/// <typeparam name="TBody">The body content type.</typeparam>
|
|
||||||
/// <typeparam name="TResult">The expected response type.</typeparam>
|
|
||||||
/// <param name="url">The request URL, optionally excluding the base URL.</param>
|
|
||||||
/// <param name="content">The body content to post.</param>
|
|
||||||
private TResult Post<TBody, TResult>(string url, TBody content)
|
|
||||||
{
|
{
|
||||||
// note: avoid HttpClient for macOS compatibility
|
this.Client.Dispose();
|
||||||
using WebClient client = new();
|
|
||||||
|
|
||||||
Uri fullUrl = new(this.BaseUrl, url);
|
|
||||||
string data = JsonConvert.SerializeObject(content);
|
|
||||||
|
|
||||||
client.Headers["Content-Type"] = "application/json";
|
|
||||||
client.Headers["User-Agent"] = $"SMAPI/{this.Version}";
|
|
||||||
string response = client.UploadString(fullUrl, data);
|
|
||||||
return JsonConvert.DeserializeObject<TResult>(response, this.JsonSettings)
|
|
||||||
?? throw new InvalidOperationException($"Could not parse the response from POST {url}.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,27 @@ namespace StardewModdingAPI.Toolkit.Serialization
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
/// <summary>The JSON settings to use when serializing and deserializing files.</summary>
|
/// <summary>The JSON settings to use when serializing and deserializing files.</summary>
|
||||||
public JsonSerializerSettings JsonSettings { get; } = new()
|
public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings();
|
||||||
{
|
|
||||||
Formatting = Formatting.Indented,
|
|
||||||
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
|
|
||||||
Converters = new List<JsonConverter>
|
|
||||||
{
|
|
||||||
new SemanticVersionConverter(),
|
|
||||||
new StringEnumConverter()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Create an instance of the default JSON serializer settings.</summary>
|
||||||
|
public static JsonSerializerSettings CreateDefaultSettings()
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Formatting = Formatting.Indented,
|
||||||
|
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
|
||||||
|
Converters = new List<JsonConverter>
|
||||||
|
{
|
||||||
|
new SemanticVersionConverter(),
|
||||||
|
new StringEnumConverter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Read a JSON file.</summary>
|
/// <summary>Read a JSON file.</summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
/// <param name="fullPath">The absolute file path.</param>
|
/// <param name="fullPath">The absolute file path.</param>
|
||||||
|
|
|
@ -199,7 +199,7 @@ namespace StardewModdingAPI.Web
|
||||||
/// <param name="settings">The serializer settings to edit.</param>
|
/// <param name="settings">The serializer settings to edit.</param>
|
||||||
private void ConfigureJsonNet(JsonSerializerSettings settings)
|
private void ConfigureJsonNet(JsonSerializerSettings settings)
|
||||||
{
|
{
|
||||||
foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters)
|
foreach (JsonConverter converter in JsonHelper.CreateDefaultSettings().Converters)
|
||||||
settings.Converters.Add(converter);
|
settings.Converters.Add(converter);
|
||||||
|
|
||||||
settings.Formatting = Formatting.Indented;
|
settings.Formatting = Formatting.Indented;
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System.Runtime.ExceptionServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
#if SMAPI_FOR_WINDOWS
|
#if SMAPI_FOR_WINDOWS
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
@ -406,7 +407,7 @@ namespace StardewModdingAPI.Framework
|
||||||
this.CheckForSoftwareConflicts();
|
this.CheckForSoftwareConflicts();
|
||||||
|
|
||||||
// check for updates
|
// check for updates
|
||||||
this.CheckForUpdatesAsync(mods);
|
_ = this.CheckForUpdatesAsync(mods); // ignore task since the main thread doesn't need to wait for it
|
||||||
}
|
}
|
||||||
|
|
||||||
// update window titles
|
// update window titles
|
||||||
|
@ -1450,16 +1451,15 @@ namespace StardewModdingAPI.Framework
|
||||||
|
|
||||||
/// <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>
|
/// <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>
|
/// <param name="mods">The mods to include in the update check (if eligible).</param>
|
||||||
private void CheckForUpdatesAsync(IModMetadata[] mods)
|
private async Task CheckForUpdatesAsync(IModMetadata[] mods)
|
||||||
{
|
{
|
||||||
if (!this.Settings.CheckForUpdates)
|
try
|
||||||
return;
|
|
||||||
|
|
||||||
new Thread(() =>
|
|
||||||
{
|
{
|
||||||
|
if (!this.Settings.CheckForUpdates)
|
||||||
|
return;
|
||||||
|
|
||||||
// create client
|
// create client
|
||||||
string url = this.Settings.WebApiBaseUrl;
|
using WebApiClient client = new(this.Settings.WebApiBaseUrl, Constants.ApiVersion);
|
||||||
WebApiClient client = new(url, Constants.ApiVersion);
|
|
||||||
this.Monitor.Log("Checking for updates...");
|
this.Monitor.Log("Checking for updates...");
|
||||||
|
|
||||||
// check SMAPI version
|
// check SMAPI version
|
||||||
|
@ -1469,9 +1469,15 @@ namespace StardewModdingAPI.Framework
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// fetch update check
|
// fetch update check
|
||||||
ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value;
|
IDictionary<string, ModEntryModel> response = await client.GetModInfoAsync(
|
||||||
updateFound = response.SuggestedUpdate?.Version;
|
mods: new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) },
|
||||||
updateUrl = response.SuggestedUpdate?.Url;
|
apiVersion: Constants.ApiVersion,
|
||||||
|
gameVersion: Constants.GameVersion,
|
||||||
|
platform: Constants.Platform
|
||||||
|
);
|
||||||
|
ModEntryModel updateInfo = response.Single().Value;
|
||||||
|
updateFound = updateInfo.SuggestedUpdate?.Version;
|
||||||
|
updateUrl = updateInfo.SuggestedUpdate?.Url;
|
||||||
|
|
||||||
// log message
|
// log message
|
||||||
if (updateFound != null)
|
if (updateFound != null)
|
||||||
|
@ -1480,10 +1486,10 @@ namespace StardewModdingAPI.Framework
|
||||||
this.Monitor.Log(" SMAPI okay.");
|
this.Monitor.Log(" SMAPI okay.");
|
||||||
|
|
||||||
// show errors
|
// show errors
|
||||||
if (response.Errors.Any())
|
if (updateInfo.Errors.Any())
|
||||||
{
|
{
|
||||||
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.", LogLevel.Warn);
|
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.", LogLevel.Warn);
|
||||||
this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}");
|
this.Monitor.Log($"Error: {string.Join("\n", updateInfo.Errors)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -1523,7 +1529,7 @@ namespace StardewModdingAPI.Framework
|
||||||
|
|
||||||
// fetch results
|
// fetch results
|
||||||
this.Monitor.Log($" Checking for updates to {searchMods.Count} mods...");
|
this.Monitor.Log($" Checking for updates to {searchMods.Count} mods...");
|
||||||
IDictionary<string, ModEntryModel> results = client.GetModInfo(searchMods.ToArray(), apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform);
|
IDictionary<string, ModEntryModel> results = await client.GetModInfoAsync(searchMods.ToArray(), apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform);
|
||||||
|
|
||||||
// extract update alerts & errors
|
// extract update alerts & errors
|
||||||
var updates = new List<Tuple<IModMetadata, ISemanticVersion, string>>();
|
var updates = new List<Tuple<IModMetadata, ISemanticVersion, string>>();
|
||||||
|
@ -1573,7 +1579,15 @@ namespace StardewModdingAPI.Framework
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).Start();
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Monitor.Log("Couldn't check for updates. This won't affect your game, but you won't be notified of SMAPI or mod updates if this keeps happening.", LogLevel.Warn);
|
||||||
|
this.Monitor.Log(ex is WebException && ex.InnerException == null
|
||||||
|
? ex.Message
|
||||||
|
: ex.ToString()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Create a directory path if it doesn't exist.</summary>
|
/// <summary>Create a directory path if it doesn't exist.</summary>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
|
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
|
||||||
<PackageReference Include="MonoMod.Common" Version="22.3.5.1" />
|
<PackageReference Include="MonoMod.Common" Version="22.3.5.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
|
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" />
|
||||||
<PackageReference Include="Pintail" Version="2.2.0" />
|
<PackageReference Include="Pintail" Version="2.2.0" />
|
||||||
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
|
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
|
||||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||||
|
|
Loading…
Reference in New Issue