diff options
Diffstat (limited to 'src/SMAPI.Web')
-rw-r--r-- | src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/LogParsing/LogParser.cs | 6 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs | 52 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs | 3 | ||||
-rw-r--r-- | src/SMAPI.Web/SMAPI.Web.csproj | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/Startup.cs | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 124 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/css/log-parser.css | 34 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/SMAPI.metadata.json | 10 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 4 |
10 files changed, 162 insertions, 77 deletions
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 55272b23..0efa62c5 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -77,8 +77,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing }; // parse log messages - LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true); - LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true); + LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true, isMod: false); + LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true, isMod: false); IDictionary<string, List<LogModInfo>> mods = new Dictionary<string, List<LogModInfo>>(); bool inModList = false; bool inContentPackList = false; @@ -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 a6b9165c..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> @@ -39,9 +48,15 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models [MemberNotNullWhen(true, nameof(LogModInfo.UpdateVersion), nameof(LogModInfo.UpdateLink))] public bool HasUpdate => this.UpdateVersion != null && this.Version != this.UpdateVersion; - /// <summary>Whether the mod is a content pack for another mod.</summary> + /// <summary>Whether this is an actual mod (rather than a special entry for SMAPI or the game itself).</summary> + public bool IsMod { get; } + + /// <summary>Whether this is a C# code mod.</summary> + public bool IsCodeMod { get; } + + /// <summary>Whether this is a content pack for another mod.</summary> [MemberNotNullWhen(true, nameof(LogModInfo.ContentPackFor))] - public bool IsContentPack => !string.IsNullOrWhiteSpace(this.ContentPackFor); + public bool IsContentPack { get; } /********* @@ -57,17 +72,26 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /// <param name="contentPackFor">The name of the mod for which this is a content pack (if applicable).</param> /// <param name="errors">The number of errors logged by this mod.</param> /// <param name="loaded">Whether the mod was loaded into the game.</param> - public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true) + /// <param name="isMod">Whether this is an actual mod (instead of a special entry for SMAPI or the game).</param> + public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true, bool isMod = true) { this.Name = name; this.Author = author; - this.Version = version; this.Description = description; this.UpdateVersion = updateVersion; this.UpdateLink = updateLink; this.ContentPackFor = contentPackFor; this.Errors = errors; this.Loaded = loaded; + + if (isMod) + { + this.IsMod = true; + this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor); + this.IsCodeMod = !this.IsContentPack; + } + + this.OverrideVersion(version); } /// <summary>Add an update alert for this mod.</summary> @@ -81,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 index 6951e434..3f649199 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs @@ -28,6 +28,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /// <summary>The SMAPI version.</summary> public string? ApiVersion { get; set; } + /// <summary>The parsed SMAPI version, if it's valid.</summary> + public ISemanticVersion? ApiVersionParsed { get; set; } + /// <summary>The game version.</summary> public string? GameVersion { get; set; } diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 4c2569e1..0a460e9e 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -25,7 +25,7 @@ <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.5" /> <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" /> <PackageReference Include="Pathoschild.FluentNexus" Version="1.0.5" /> - <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" /> + <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" /> 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.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 5e55906d..b824b768 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -8,24 +8,29 @@ @{ ViewData["Title"] = "SMAPI log parser"; + // get log info ParsedLog? log = Model!.ParsedLog; - IDictionary<string, LogModInfo[]> contentPacks = Model.GetContentPacksByMod(); + ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>()); + + // detect suggested fixes + LogModInfo[] outdatedMods = log?.Mods.Where(mod => mod.HasUpdate).ToArray() ?? Array.Empty<LogModInfo>(); + LogModInfo? errorHandler = log?.Mods.FirstOrDefault(p => p.IsCodeMod && p.Name == "Error Handler"); + bool hasOlderErrorHandler = errorHandler?.GetParsedVersion() is not null && log?.ApiVersionParsed is not null && log.ApiVersionParsed.IsNewerThan(errorHandler.GetParsedVersion()); + + // get filters IDictionary<string, bool> defaultFilters = Enum .GetValues<LogLevel>() .ToDictionary(level => level.ToString().ToLower(), level => level != LogLevel.Trace); - IDictionary<int, string> logLevels = Enum .GetValues<LogLevel>() .ToDictionary(level => (int)level, level => level.ToString().ToLower()); - IDictionary<int, string> logSections = Enum .GetValues<LogSection>() .ToDictionary(section => (int)section, section => section.ToString()); + // get form string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true)!; - - ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>()); } @section Head { @@ -34,7 +39,7 @@ <meta name="robots" content="noindex" /> } <link rel="stylesheet" href="~/Content/css/file-upload.css" /> - <link rel="stylesheet" href="~/Content/css/log-parser.css?r=20220409" /> + <link rel="stylesheet" href="~/Content/css/log-parser.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3/dist/css/tabby-ui-vertical.min.css" /> <script src="https://cdn.jsdelivr.net/npm/tabbyjs@12.0.3" crossorigin="anonymous"></script> @@ -69,7 +74,7 @@ </text> } </script> - + <script> $(function() { smapi.logParser( @@ -158,7 +163,7 @@ else if (log?.IsValid == true) <div id="os-instructions"> <div> <ul data-tabs> - @foreach (Platform platform in new[] {Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows}) + @foreach (Platform platform in new[] { Platform.Android, Platform.Linux, Platform.Mac, Platform.Windows }) { @if (platform == Platform.Windows) { @@ -237,55 +242,66 @@ else if (log?.IsValid == true) @if (log?.IsValid == true) { <div id="output"> - @if (log.Mods.Any(mod => mod.HasUpdate)) + @if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler) { <h2>Suggested fixes</h2> <ul id="fix-list"> - <li> - Consider updating these mods to fix problems: + @if (errorHandler is null) + { + <li class="important">You don't have the <strong>Error Handler</strong> mod installed. This automatically prevents many game or mod errors. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to re-add it.</li> + } + @if (hasOlderErrorHandler) + { + <li>Your <strong>Error Handler</strong> mod is older than SMAPI. You may be missing some game/mod error fixes. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to update it.</li> + } + @if (outdatedMods.Any()) + { + <li> + Consider updating these mods to fix problems: - <table id="updates" class="table"> - @foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate)))) - { - <tr class="mod-entry"> - <td> - <strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong> - @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList)) - { - <div class="content-packs"> - @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) - { - <text>+ @contentPack.Name</text><br /> - } - </div> - } - </td> - <td> - @if (mod.HasUpdate) - { - <a href="@mod.UpdateLink" target="_blank"> - @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") - </a> - } - else - { - <text> </text> - } + <table id="updates" class="table"> + @foreach (LogModInfo mod in log.Mods.Where(mod => (mod.HasUpdate && !mod.IsContentPack) || (contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) && contentPackList.Any(pack => pack.HasUpdate)))) + { + <tr class="mod-entry"> + <td> + <strong class=@(!mod.HasUpdate ? "hidden" : "")>@mod.Name</strong> + @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList)) + { + <div class="content-packs"> + @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) + { + <text>+ @contentPack.Name</text><br /> + } + </div> + } + </td> + <td> + @if (mod.HasUpdate) + { + <a href="@mod.UpdateLink" target="_blank"> + @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") + </a> + } + else + { + <text> </text> + } - @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList)) - { - <div> - @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) - { - <a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br /> - } - </div> - } - </td> - </tr> - } - </table> - </li> + @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out contentPackList)) + { + <div> + @foreach (LogModInfo contentPack in contentPackList.Where(pack => pack.HasUpdate)) + { + <a href="@contentPack.UpdateLink" target="_blank">@contentPack.Version → @contentPack.UpdateVersion</a><br /> + } + </div> + } + </td> + </tr> + } + </table> + </li> + } </ul> } @@ -293,7 +309,7 @@ else if (log?.IsValid == true) <table id="metadata" class="table" - data-code-mods="@log.Mods.Count(p => !p.IsContentPack)" + data-code-mods="@log.Mods.Count(p => p.IsCodeMod)" data-content-packs="@log.Mods.Count(p => p.IsContentPack)" data-os="@log.OperatingSystem" data-game-version="@log.GameVersion" @@ -434,7 +450,7 @@ else if (log?.IsValid == true) <div> This website uses JavaScript to display a filterable table. To view this log, please enable JavaScript or <a href="@this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID, format = LogViewFormat.RawView })">view the raw log</a>. </div> - <br/> + <br /> </noscript> <log-table> diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css index 1d457e35..f136a96f 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css +++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css @@ -54,6 +54,36 @@ table caption { } /********* +** Suggested fixes +*********/ +#fix-list { + padding-left: 1em; + margin-bottom: 2em; +} + +#fix-list li { + padding: 0.5em; + background: #FFC; + border: 1px solid #880; + border-radius: 5px; + list-style-type: none; +} + +#fix-list li:not(:last-child) { + margin-bottom: 0.5em; +} + +#fix-list li.important { + background: #FCC; + border-color: #800; +} + +#fix-list li::before { + content: "⚠ "; +} + + +/********* ** Log metadata & filters *********/ .table, #filters { @@ -84,10 +114,6 @@ table caption { min-height: 1.3em; } -#fix-list { - margin-bottom: 2em; -} - #updates { min-width: 10em; } diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index 16a89647..d654b181 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -107,12 +107,6 @@ "Default | UpdateKey": "Nexus:1726" }, - "Rubydew": { - "ID": "bwdy.rubydew", - "SuppressWarnings": "UsesDynamic", // mod explicitly loads DLLs for Linux/macOS compatibility - "Default | UpdateKey": "Nexus:3656" - }, - "SpaceCore": { "ID": "spacechase0.SpaceCore", "Default | UpdateKey": "Nexus:1348" @@ -172,8 +166,8 @@ *********/ "CFAutomate": { "ID": "Platonymous.CFAutomate", - "~2.12.9 | Status": "AssumeBroken", - "~2.12.9 | StatusReasonDetails": "causes runtime errors in newer versions of Automate" + "~2.12.11 | Status": "AssumeBroken", + "~2.12.11 | StatusReasonDetails": "causes runtime errors in newer versions of Automate" }, "Dynamic Game Assets": { "ID": "spacechase0.DynamicGameAssets", diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index f0fe74c2..631fbc63 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -14,9 +14,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.", "type": "string", - "const": "1.26.0", + "pattern": "^1\\.27\\.[0-9]+$", "@errorMessages": { - "const": "Incorrect value '@value'. You should always use the latest format version (currently 1.26.0) to enable the latest features, avoid obsolete behavior, and reduce load times." + "pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.27.0) to enable the latest features, avoid obsolete behavior, and reduce load times." } }, "ConfigSchema": { |