diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-07-06 22:26:09 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-07-06 22:26:09 -0400 |
commit | d51ffe58f7b7450cd4c4a7ee3d8b4da1cf55e7e4 (patch) | |
tree | ec19cdd7567125e47cfd49c7c044fc09f09f6b2f /src/SMAPI.Toolkit | |
parent | 8e9237bdd7ec179975c9be5e28c811b42007e707 (diff) | |
parent | bcb9e25d8666d2c1384515063ffbf987c36b8b0e (diff) | |
download | SMAPI-d51ffe58f7b7450cd4c4a7ee3d8b4da1cf55e7e4.tar.gz SMAPI-d51ffe58f7b7450cd4c4a7ee3d8b4da1cf55e7e4.tar.bz2 SMAPI-d51ffe58f7b7450cd4c4a7ee3d8b4da1cf55e7e4.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Toolkit')
9 files changed, 66 insertions, 57 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/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 7f06d170..3bdd145a 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -283,8 +283,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki } /// <summary>The response model for the MediaWiki parse API.</summary> - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialization.")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialization.")] private class ResponseModel { /********* @@ -306,9 +306,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki } /// <summary>The inner response model for the MediaWiki parse API.</summary> - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialization.")] + [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local", Justification = "Used via JSON deserialization.")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialization.")] private class ResponseParseModel { /********* diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 6978567e..f464f4bb 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -21,7 +21,8 @@ namespace StardewModdingAPI.Toolkit.Framework /// <summary>Get the OS name from the system uname command.</summary> /// <param name="buffer">The buffer to fill with the resulting string.</param> [DllImport("libc")] - static extern int uname(IntPtr buffer); + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "This is the actual external command name.")] + private static extern int uname(IntPtr buffer); /********* @@ -51,7 +52,6 @@ namespace StardewModdingAPI.Toolkit.Framework /// <summary>Get the human-readable OS name and version.</summary> /// <param name="platform">The current platform.</param> - [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] public static string GetFriendlyPlatformName(string platform) { #if SMAPI_FOR_WINDOWS @@ -65,7 +65,10 @@ namespace StardewModdingAPI.Toolkit.Framework return result ?? "Windows"; } - catch { } + catch + { + // fallback to default behavior + } #endif string name = Environment.OSVersion.ToString(); diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs index 32c2ed6d..4c76f417 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs @@ -18,9 +18,11 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// <summary>The mod patches the game in a way that may impact stability.</summary> PatchesGame = 4, +#if SMAPI_DEPRECATED /// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/macOS.</summary> [Obsolete("This value is no longer used by SMAPI and will be removed in the upcoming SMAPI 4.0.0.")] UsesDynamic = 8, +#endif /// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary> UsesUnvalidatedUpdateTick = 16, @@ -37,6 +39,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// <summary>Uses .NET APIs for shell or process access.</summary> AccessesShell = 256, +#if SMAPI_DEPRECATED /// <summary>References the legacy <c>System.Configuration.ConfigurationManager</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary> DetectedLegacyConfigurationDll = 512, @@ -45,5 +48,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// <summary>References the legacy <c>System.Security.Permissions</c> assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.</summary> DetectedLegacyPermissionsDll = 2048 +#endif } } diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index 0df75a31..55b9bdd8 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Toolkit /// <param name="metadataPath">The file path for the SMAPI metadata file.</param> public ModDatabase GetModDatabase(string metadataPath) { - MetadataModel metadata = JsonConvert.DeserializeObject<MetadataModel>(File.ReadAllText(metadataPath)); + MetadataModel metadata = JsonConvert.DeserializeObject<MetadataModel>(File.ReadAllText(metadataPath)) ?? new MetadataModel(); ModDataRecord[] records = metadata.ModData.Select(pair => new ModDataRecord(pair.Key, pair.Value)).ToArray(); return new ModDatabase(records, this.GetUpdateUrl); } diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index e021993f..7b79105f 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -11,7 +11,7 @@ <ItemGroup> <PackageReference Include="HtmlAgilityPack" Version="1.11.43" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> - <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" /> + <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" /> <PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" /> </ItemGroup> diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs index c32c3185..913d54e0 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs @@ -48,7 +48,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters return this.ReadObject(JObject.Load(reader)); case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value<string>(), path); + { + string? value = JToken.Load(reader).Value<string>(); + return value is not null + ? this.ReadString(value, path) + : null; + } default: throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path})."); diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs index 1c59f5e7..cdf2ed77 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs @@ -42,7 +42,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters return this.ReadObject(JObject.Load(reader), path); case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value<string>(), path); + { + string? value = JToken.Load(reader).Value<string>(); + return value is not null + ? this.ReadString(value, path) + : null; + } default: throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path})."); 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> |