summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs13
-rw-r--r--src/SMAPI.ModBuildConfig/DeployModTask.cs3
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs56
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs10
-rw-r--r--src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs9
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs2
-rw-r--r--src/SMAPI.Toolkit/ModToolkit.cs2
-rw-r--r--src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs7
-rw-r--r--src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs7
-rw-r--r--src/SMAPI.Toolkit/Serialization/JsonHelper.cs26
-rw-r--r--src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs2
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogParser.cs2
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs32
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs3
-rw-r--r--src/SMAPI.Web/Startup.cs2
-rw-r--r--src/SMAPI.Web/Views/LogParser/Index.cshtml127
-rw-r--r--src/SMAPI.Web/wwwroot/Content/css/log-parser.css34
-rw-r--r--src/SMAPI.Web/wwwroot/SMAPI.metadata.json10
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs3
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs27
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs1
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs4
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs1
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs2
-rw-r--r--src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs2
-rw-r--r--src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs54
-rw-r--r--src/SMAPI/Framework/SGame.cs3
-rw-r--r--src/SMAPI/Framework/Serialization/KeybindConverter.cs5
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs66
-rw-r--r--src/SMAPI/SMAPI.csproj1
-rw-r--r--src/SMAPI/Translation.cs2
-rw-r--r--src/SMAPI/Utilities/SDate.cs6
41 files changed, 320 insertions, 228 deletions
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index 5a6aa747..d00a5df4 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -206,7 +206,7 @@ namespace StardewModdingApi.Installer
Console.WriteLine();
// handle choice
- string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" });
+ string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }, printLine: Console.WriteLine);
switch (choice)
{
case "1":
@@ -453,6 +453,7 @@ namespace StardewModdingApi.Installer
}
// find target folder
+ // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract -- avoid error if the Mods folder has invalid mods, since they're not validated yet
ModFolder? targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true);
DirectoryInfo defaultTargetFolder = new(Path.Combine(paths.ModsPath, sourceMod.Directory.Name));
DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder;
@@ -628,22 +629,22 @@ namespace StardewModdingApi.Installer
}
/// <summary>Interactively ask the user to choose a value.</summary>
- /// <param name="print">A callback which prints a message to the console.</param>
+ /// <param name="printLine">A callback which prints a message to the console.</param>
/// <param name="message">The message to print.</param>
/// <param name="options">The allowed options (not case sensitive).</param>
/// <param name="indent">The indentation to prefix to output.</param>
- private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string>? print = null)
+ private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string>? printLine = null)
{
- print ??= this.PrintInfo;
+ printLine ??= this.PrintInfo;
while (true)
{
- print(indent + message);
+ printLine(indent + message);
Console.Write(indent);
string? input = Console.ReadLine()?.Trim().ToLowerInvariant();
if (input == null || !options.Contains(input))
{
- print($"{indent}That's not a valid option.");
+ printLine($"{indent}That's not a valid option.");
continue;
}
return input;
diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs
index c7026ee1..88412d92 100644
--- a/src/SMAPI.ModBuildConfig/DeployModTask.cs
+++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs
@@ -227,8 +227,7 @@ namespace StardewModdingAPI.ModBuildConfig
string fromPath = entry.Value.FullName;
string toPath = Path.Combine(modFolderPath, entry.Key);
- // ReSharper disable once AssignNullToNotNullAttribute -- not applicable in this context
- Directory.CreateDirectory(Path.GetDirectoryName(toPath));
+ Directory.CreateDirectory(Path.GetDirectoryName(toPath)!);
File.Copy(fromPath, toPath, overwrite: true);
}
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs
index 3722e155..88ddfe6b 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs
@@ -30,7 +30,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework
/// <summary>Get all spawnable items.</summary>
/// <param name="itemTypes">The item types to fetch (or null for any type).</param>
/// <param name="includeVariants">Whether to include flavored variants like "Sunflower Honey".</param>
- [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")]
+ [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = $"{nameof(ItemRepository.TryCreate)} invokes the lambda immediately.")]
public IEnumerable<SearchableItem> GetAll(ItemType[]? itemTypes = null, bool includeVariants = true)
{
//
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 300de9d2..3c2dec19 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "3.15.0",
+ "Version": "3.15.1",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.15.0"
+ "MinimumApiVersion": "3.15.1"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index 15a1e0f3..28b4b149 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.15.0",
+ "Version": "3.15.1",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.15.0"
+ "MinimumApiVersion": "3.15.1"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 1a11742c..1944575b 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "3.15.0",
+ "Version": "3.15.1",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.15.0"
+ "MinimumApiVersion": "3.15.1"
}
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 338192af..4c76f417 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs
@@ -18,7 +18,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// <summary>The mod patches the game in a way that may impact stability.</summary>
PatchesGame = 4,
-#if SMAPI_FOR_WINDOWS
+#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,
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/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>
diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs
index c60b2c90..f5a5f930 100644
--- a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs
+++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs
@@ -42,7 +42,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
/// <summary>Get update check info about a mod.</summary>
/// <param name="id">The mod ID.</param>
- [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this method.")]
+ [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The nullability is validated in this method.")]
public async Task<IModPage?> GetModData(string id)
{
IModPage page = new GenericModPage(this.SiteKey, id);
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
index 7fc8f958..0efa62c5 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
@@ -200,6 +200,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
log.GameVersion = match.Groups["gameVersion"].Value;
log.OperatingSystem = match.Groups["os"].Value;
smapiMod.OverrideVersion(log.ApiVersion);
+
+ log.ApiVersionParsed = smapiMod.GetParsedVersion();
}
// mod path line
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
index 4b80a830..557f08ff 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
@@ -1,4 +1,6 @@
+using System;
using System.Diagnostics.CodeAnalysis;
+using StardewModdingAPI.Toolkit;
namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
@@ -6,6 +8,13 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
public class LogModInfo
{
/*********
+ ** Private fields
+ *********/
+ /// <summary>The parsed mod version, if valid.</summary>
+ private Lazy<ISemanticVersion?> ParsedVersionImpl;
+
+
+ /*********
** Accessors
*********/
/// <summary>The mod name.</summary>
@@ -68,7 +77,6 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
{
this.Name = name;
this.Author = author;
- this.Version = version;
this.Description = description;
this.UpdateVersion = updateVersion;
this.UpdateLink = updateLink;
@@ -82,6 +90,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor);
this.IsCodeMod = !this.IsContentPack;
}
+
+ this.OverrideVersion(version);
}
/// <summary>Add an update alert for this mod.</summary>
@@ -95,9 +105,29 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
/// <summary>Override the version number, for cases like SMAPI itself where the version is only known later during parsing.</summary>
/// <param name="version">The new mod version.</param>
+ [MemberNotNull(nameof(LogModInfo.Version), nameof(LogModInfo.ParsedVersionImpl))]
public void OverrideVersion(string version)
{
this.Version = version;
+ this.ParsedVersionImpl = new Lazy<ISemanticVersion?>(this.ParseVersion);
+ }
+
+ /// <summary>Get the semantic version for this mod, if it's valid.</summary>
+ public ISemanticVersion? GetParsedVersion()
+ {
+ return this.ParsedVersionImpl.Value;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get the semantic version for this mod, if it's valid.</summary>
+ public ISemanticVersion? ParseVersion()
+ {
+ return !string.IsNullOrWhiteSpace(this.Version) && SemanticVersion.TryParse(this.Version, out ISemanticVersion? version)
+ ? version
+ : null;
}
}
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs<