summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs56
-rw-r--r--src/SMAPI.Toolkit/Serialization/JsonHelper.cs26
-rw-r--r--src/SMAPI.Web/Startup.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs44
-rw-r--r--src/SMAPI/SMAPI.csproj1
5 files changed, 68 insertions, 61 deletions
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
index d4282617..ef1904d4 100644
--- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
@@ -1,27 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Net;
-using Newtonsoft.Json;
+using System.Threading.Tasks;
+using Pathoschild.Http.Client;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
{
/// <summary>Provides methods for interacting with the SMAPI web API.</summary>
- public class WebApiClient
+ public class WebApiClient : IDisposable
{
/*********
** Fields
*********/
- /// <summary>The base URL for the web API.</summary>
- private readonly Uri BaseUrl;
-
/// <summary>The API version number.</summary>
private readonly ISemanticVersion Version;
- /// <summary>The JSON serializer settings to use.</summary>
- private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings;
+ /// <summary>The underlying HTTP client.</summary>
+ private readonly IClient Client;
/*********
@@ -32,8 +29,11 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
/// <param name="version">The web API version.</param>
public WebApiClient(string baseUrl, ISemanticVersion version)
{
- this.BaseUrl = new Uri(baseUrl);
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>
@@ -42,36 +42,22 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
/// <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="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[]>(
- $"v{this.Version}/mods",
- new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
- ).ToDictionary(p => p.ID);
+ ModEntryModel[] result = await this.Client
+ .PostAsync(
+ $"v{this.Version}/mods",
+ new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
+ )
+ .As<ModEntryModel[]>();
+
+ return result.ToDictionary(p => p.ID);
}
-
- /*********
- ** 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)
+ /// <inheritdoc />
+ public void Dispose()
{
- // note: avoid HttpClient for macOS compatibility
- 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}.");
+ this.Client.Dispose();
}
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
index 1a003c51..a5d7e2e8 100644
--- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
+++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
@@ -15,21 +15,27 @@ namespace StardewModdingAPI.Toolkit.Serialization
** Accessors
*********/
/// <summary>The JSON settings to use when serializing and deserializing files.</summary>
- public JsonSerializerSettings JsonSettings { get; } = 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()
- }
- };
+ public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings();
/*********
** 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>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="fullPath">The absolute file path.</param>
diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs
index 9980d00c..54c25979 100644
--- a/src/SMAPI.Web/Startup.cs
+++ b/src/SMAPI.Web/Startup.cs
@@ -199,7 +199,7 @@ namespace StardewModdingAPI.Web
/// <param name="settings">The serializer settings to edit.</param>
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.Formatting = Formatting.Indented;
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 16c168a0..fdfe70fc 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -10,6 +10,7 @@ using System.Runtime.ExceptionServices;
using System.Security;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Xna.Framework;
#if SMAPI_FOR_WINDOWS
using Microsoft.Win32;
@@ -406,7 +407,7 @@ namespace StardewModdingAPI.Framework
this.CheckForSoftwareConflicts();
// 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
@@ -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>
/// <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)
- return;
-
- new Thread(() =>
+ try
{
+ if (!this.Settings.CheckForUpdates)
+ return;
+
// create client
- string url = this.Settings.WebApiBaseUrl;
- WebApiClient client = new(url, Constants.ApiVersion);
+ using WebApiClient client = new(this.Settings.WebApiBaseUrl, Constants.ApiVersion);
this.Monitor.Log("Checking for updates...");
// check SMAPI version
@@ -1469,9 +1469,15 @@ namespace StardewModdingAPI.Framework
try
{
// 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;
- updateFound = response.SuggestedUpdate?.Version;
- updateUrl = response.SuggestedUpdate?.Url;
+ IDictionary<string, ModEntryModel> response = await client.GetModInfoAsync(
+ mods: new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) },
+ apiVersion: Constants.ApiVersion,
+ gameVersion: Constants.GameVersion,
+ platform: Constants.Platform
+ );
+ ModEntryModel updateInfo = response.Single().Value;
+ updateFound = updateInfo.SuggestedUpdate?.Version;
+ updateUrl = updateInfo.SuggestedUpdate?.Url;
// log message
if (updateFound != null)
@@ -1480,10 +1486,10 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log(" SMAPI okay.");
// 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($"Error: {string.Join("\n", response.Errors)}");
+ this.Monitor.Log($"Error: {string.Join("\n", updateInfo.Errors)}");
}
}
catch (Exception ex)
@@ -1523,7 +1529,7 @@ namespace StardewModdingAPI.Framework
// fetch results
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
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>
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index 3abefeab..c05512e9 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -25,6 +25,7 @@
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="MonoMod.Common" Version="22.3.5.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="Platonymous.TMXTile" Version="1.5.9" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />