From a090b6c21c877e8835f25e1d70d667abf07d1d3c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 May 2020 11:29:40 -0400 Subject: use newer C# features --- .../Controllers/JsonValidatorController.cs | 32 ++++++++++------------ src/SMAPI.Web/Controllers/ModsApiController.cs | 4 +-- 2 files changed, 16 insertions(+), 20 deletions(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index 2ade3e3d..c43fb929 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -275,21 +275,20 @@ namespace StardewModdingAPI.Web.Controllers errors = new Dictionary(errors, StringComparer.InvariantCultureIgnoreCase); // match error by type and message - foreach (var pair in errors) + foreach ((string target, string errorMessage) in errors) { - if (!pair.Key.Contains(":")) + if (!target.Contains(":")) continue; - string[] parts = pair.Key.Split(':', 2); + string[] parts = target.Split(':', 2); if (parts[0].Equals(error.ErrorType.ToString(), StringComparison.InvariantCultureIgnoreCase) && Regex.IsMatch(error.Message, parts[1])) - return pair.Value?.Trim(); + return errorMessage?.Trim(); } // match by type - if (errors.TryGetValue(error.ErrorType.ToString(), out string message)) - return message?.Trim(); - - return null; + return errors.TryGetValue(error.ErrorType.ToString(), out string message) + ? message?.Trim() + : null; } return GetRawOverrideError() @@ -304,10 +303,10 @@ namespace StardewModdingAPI.Web.Controllers { if (schema.ExtensionData != null) { - foreach (var pair in schema.ExtensionData) + foreach ((string curKey, JToken value) in schema.ExtensionData) { - if (pair.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)) - return pair.Value.ToObject(); + if (curKey.Equals(key, StringComparison.InvariantCultureIgnoreCase)) + return value.ToObject(); } } @@ -318,14 +317,11 @@ namespace StardewModdingAPI.Web.Controllers /// The value to format. private string FormatValue(object value) { - switch (value) + return value switch { - case List list: - return string.Join(", ", list); - - default: - return value?.ToString() ?? "null"; - } + List list => string.Join(", ", list), + _ => value?.ToString() ?? "null" + }; } } } diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 06768f03..e841a075 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -388,9 +388,9 @@ namespace StardewModdingAPI.Web.Controllers if (map.ContainsKey(parsed.ToString())) return map[parsed.ToString()]; - foreach (var pair in map) + foreach ((string fromRaw, string toRaw) in map) { - if (SemanticVersion.TryParse(pair.Key, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(pair.Value, allowNonStandard, out ISemanticVersion newVersion)) + if (SemanticVersion.TryParse(fromRaw, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(toRaw, allowNonStandard, out ISemanticVersion newVersion)) return newVersion.ToString(); } } -- cgit From c776f6053bd0b9db909ebda2853a86c1cd21c2cf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 May 2020 11:33:17 -0400 Subject: update deprecated code --- src/SMAPI.Web/Controllers/ModsApiController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index e841a075..6032186f 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -61,7 +61,7 @@ namespace StardewModdingAPI.Web.Controllers /// The GitHub API client. /// The ModDrop API client. /// The Nexus API client. - public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions config, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus) + public ModsApiController(IWebHostEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions config, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus) { this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json")); -- cgit From d7add894419543667e60569bfeb439e8e797a4d1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 May 2020 19:25:34 -0400 Subject: drop MongoDB code MongoDB support unnecessarily complicated the code and there's no need to run distributed servers in the foreseeable future. This keeps the abstract storage interface so we can wrap a distributed cache in the future. --- src/SMAPI.Web/Controllers/ModsApiController.cs | 26 +++++++++++++++----------- src/SMAPI.Web/Controllers/ModsController.cs | 9 +++++---- 2 files changed, 20 insertions(+), 15 deletions(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 6032186f..b9d7c32d 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -12,6 +12,7 @@ using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Framework.UpdateData; using StardewModdingAPI.Web.Framework; +using StardewModdingAPI.Web.Framework.Caching; using StardewModdingAPI.Web.Framework.Caching.Mods; using StardewModdingAPI.Web.Framework.Caching.Wiki; using StardewModdingAPI.Web.Framework.Clients.Chucklefish; @@ -90,7 +91,7 @@ namespace StardewModdingAPI.Web.Controllers return new ModEntryModel[0]; // fetch wiki data - WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.GetModel()).ToArray(); + WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.Data).ToArray(); IDictionary mods = new Dictionary(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in model.Mods) { @@ -283,27 +284,30 @@ namespace StardewModdingAPI.Web.Controllers /// Whether to allow non-standard versions. private async Task GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions) { - // get mod - if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) + // get from cache + if (this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out Cached cachedMod) && !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) + return cachedMod.Data; + + // fetch from mod site { // get site if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository)) return new ModInfoModel().SetError(RemoteModStatus.DoesNotExist, $"There's no mod site with key '{updateKey.Repository}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}]."); // fetch mod - ModInfoModel result = await repository.GetModInfoAsync(updateKey.ID); - if (result.Error == null) + ModInfoModel mod = await repository.GetModInfoAsync(updateKey.ID); + if (mod.Error == null) { - if (result.Version == null) - result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); - else if (!SemanticVersion.TryParse(result.Version, allowNonStandardVersions, out _)) - result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{result.Version}'."); + if (mod.Version == null) + mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); + else if (!SemanticVersion.TryParse(mod.Version, allowNonStandardVersions, out _)) + mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{mod.Version}'."); } // cache mod - this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, result, out mod); + this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, mod); + return mod; } - return mod.GetModel(); } /// Get update keys based on the available mod metadata, while maintaining the precedence order. diff --git a/src/SMAPI.Web/Controllers/ModsController.cs b/src/SMAPI.Web/Controllers/ModsController.cs index b621ded0..24e36709 100644 --- a/src/SMAPI.Web/Controllers/ModsController.cs +++ b/src/SMAPI.Web/Controllers/ModsController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using StardewModdingAPI.Web.Framework.Caching; using StardewModdingAPI.Web.Framework.Caching.Wiki; using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.ViewModels; @@ -51,16 +52,16 @@ namespace StardewModdingAPI.Web.Controllers public ModListModel FetchData() { // fetch cached data - if (!this.Cache.TryGetWikiMetadata(out CachedWikiMetadata metadata)) + if (!this.Cache.TryGetWikiMetadata(out Cached metadata)) return new ModListModel(); // build model return new ModListModel( - stableVersion: metadata.StableVersion, - betaVersion: metadata.BetaVersion, + stableVersion: metadata.Data.StableVersion, + betaVersion: metadata.Data.BetaVersion, mods: this.Cache .GetWikiMods() - .Select(mod => new ModModel(mod.GetModel())) + .Select(mod => new ModModel(mod.Data)) .OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting lastUpdated: metadata.LastUpdated, isStale: this.Cache.IsStale(metadata.LastUpdated, this.StaleMinutes) -- cgit From 786077340f2cea37d82455fc413535ae82a912ee Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 May 2020 21:55:11 -0400 Subject: refactor update check API This simplifies the logic for individual clients, centralises common logic, and prepares for upcoming features. --- src/SMAPI.Web/Controllers/ModsApiController.cs | 146 ++++++------------------- 1 file changed, 32 insertions(+), 114 deletions(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index b9d7c32d..14be520d 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -15,13 +15,13 @@ using StardewModdingAPI.Web.Framework; using StardewModdingAPI.Web.Framework.Caching; using StardewModdingAPI.Web.Framework.Caching.Mods; using StardewModdingAPI.Web.Framework.Caching.Wiki; +using StardewModdingAPI.Web.Framework.Clients; using StardewModdingAPI.Web.Framework.Clients.Chucklefish; using StardewModdingAPI.Web.Framework.Clients.CurseForge; using StardewModdingAPI.Web.Framework.Clients.GitHub; using StardewModdingAPI.Web.Framework.Clients.ModDrop; using StardewModdingAPI.Web.Framework.Clients.Nexus; using StardewModdingAPI.Web.Framework.ConfigModels; -using StardewModdingAPI.Web.Framework.ModRepositories; namespace StardewModdingAPI.Web.Controllers { @@ -33,8 +33,8 @@ namespace StardewModdingAPI.Web.Controllers /********* ** Fields *********/ - /// The mod repositories which provide mod metadata. - private readonly IDictionary Repositories; + /// The mod sites which provide mod metadata. + private readonly ModSiteManager ModSites; /// The cache in which to store wiki data. private readonly IWikiCacheRepository WikiCache; @@ -69,16 +69,7 @@ namespace StardewModdingAPI.Web.Controllers this.WikiCache = wikiCache; this.ModCache = modCache; this.Config = config; - this.Repositories = - new IModRepository[] - { - new ChucklefishRepository(chucklefish), - new CurseForgeRepository(curseForge), - new GitHubRepository(github), - new ModDropRepository(modDrop), - new NexusRepository(nexus) - } - .ToDictionary(p => p.VendorKey); + this.ModSites = new ModSiteManager(new IModSiteClient[] { chucklefish, curseForge, github, modDrop, nexus }); } /// Fetch version metadata for the given mods. @@ -149,40 +140,18 @@ namespace StardewModdingAPI.Web.Controllers } // fetch data - ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions); - if (data.Error != null) + ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions, wikiEntry?.MapRemoteVersions); + if (data.Status != RemoteModStatus.Ok) { - errors.Add(data.Error); + errors.Add(data.Error ?? data.Status.ToString()); continue; } - // handle main version - if (data.Version != null) - { - ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandardVersions); - if (version == null) - { - errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); - continue; - } - - if (this.IsNewer(version, main?.Version)) - main = new ModEntryVersionModel(version, data.Url); - } - - // handle optional version - if (data.PreviewVersion != null) - { - ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandardVersions); - if (version == null) - { - errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); - continue; - } - - if (this.IsNewer(version, optional?.Version)) - optional = new ModEntryVersionModel(version, data.Url); - } + // handle versions + if (this.IsNewer(data.Version, main?.Version)) + main = new ModEntryVersionModel(data.Version, data.Url); + if (this.IsNewer(data.PreviewVersion, optional?.Version)) + optional = new ModEntryVersionModel(data.PreviewVersion, data.Url); } // get unofficial version @@ -222,7 +191,7 @@ namespace StardewModdingAPI.Web.Controllers } // get recommended update (if any) - ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions); + ISemanticVersion installedVersion = this.ModSites.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions); if (apiVersion != null && installedVersion != null) { // get newer versions @@ -282,32 +251,27 @@ namespace StardewModdingAPI.Web.Controllers /// Get the mod info for an update key. /// The namespaced update key. /// Whether to allow non-standard versions. - private async Task GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions) + /// Maps remote versions to a semantic version for update checks. + private async Task GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions, IDictionary mapRemoteVersions) { - // get from cache - if (this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out Cached cachedMod) && !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) - return cachedMod.Data; - - // fetch from mod site + // get mod page + IModPage page; { - // get site - if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository)) - return new ModInfoModel().SetError(RemoteModStatus.DoesNotExist, $"There's no mod site with key '{updateKey.Repository}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}]."); + bool isCached = + this.ModCache.TryGetMod(updateKey.Site, updateKey.ID, out Cached cachedMod) + && !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes); - // fetch mod - ModInfoModel mod = await repository.GetModInfoAsync(updateKey.ID); - if (mod.Error == null) + if (isCached) + page = cachedMod.Data; + else { - if (mod.Version == null) - mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); - else if (!SemanticVersion.TryParse(mod.Version, allowNonStandardVersions, out _)) - mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{mod.Version}'."); + page = await this.ModSites.GetModPageAsync(updateKey); + this.ModCache.SaveMod(updateKey.Site, updateKey.ID, page); } - - // cache mod - this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, mod); - return mod; } + + // get version info + return this.ModSites.GetPageVersions(page, allowNonStandardVersions, mapRemoteVersions); } /// Get update keys based on the available mod metadata, while maintaining the precedence order. @@ -334,13 +298,13 @@ namespace StardewModdingAPI.Web.Controllers if (entry != null) { if (entry.NexusID.HasValue) - yield return $"{ModRepositoryKey.Nexus}:{entry.NexusID}"; + yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID?.ToString()); if (entry.ModDropID.HasValue) - yield return $"{ModRepositoryKey.ModDrop}:{entry.ModDropID}"; + yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID?.ToString()); if (entry.CurseForgeID.HasValue) - yield return $"{ModRepositoryKey.CurseForge}:{entry.CurseForgeID}"; + yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID?.ToString()); if (entry.ChucklefishID.HasValue) - yield return $"{ModRepositoryKey.Chucklefish}:{entry.ChucklefishID}"; + yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID?.ToString()); } } @@ -355,51 +319,5 @@ namespace StardewModdingAPI.Web.Controllers yield return key; } } - - /// Get a semantic local version for update checks. - /// The version to parse. - /// A map of version replacements. - /// Whether to allow non-standard versions. - private ISemanticVersion GetMappedVersion(string version, IDictionary map, bool allowNonStandard) - { - // try mapped version - string rawNewVersion = this.GetRawMappedVersion(version, map, allowNonStandard); - if (SemanticVersion.TryParse(rawNewVersion, allowNonStandard, out ISemanticVersion parsedNew)) - return parsedNew; - - // return original version - return SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsedOld) - ? parsedOld - : null; - } - - /// Get a semantic local version for update checks. - /// The version to map. - /// A map of version replacements. - /// Whether to allow non-standard versions. - private string GetRawMappedVersion(string version, IDictionary map, bool allowNonStandard) - { - if (version == null || map == null || !map.Any()) - return version; - - // match exact raw version - if (map.ContainsKey(version)) - return map[version]; - - // match parsed version - if (SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsed)) - { - if (map.ContainsKey(parsed.ToString())) - return map[parsed.ToString()]; - - foreach ((string fromRaw, string toRaw) in map) - { - if (SemanticVersion.TryParse(fromRaw, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(toRaw, allowNonStandard, out ISemanticVersion newVersion)) - return newVersion.ToString(); - } - } - - return version; - } } } -- cgit From d97b11060c310f1fa6064f02496e6a6162a44573 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 24 May 2020 00:21:51 -0400 Subject: add update subkeys --- src/SMAPI.Web/Controllers/ModsApiController.cs | 48 ++++++++++++++++---------- 1 file changed, 30 insertions(+), 18 deletions(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 14be520d..028fc613 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -135,7 +135,7 @@ namespace StardewModdingAPI.Web.Controllers // validate update key if (!updateKey.LooksValid) { - errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541'."); + errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541', with an optional subkey like 'Nexus:541@subkey'."); continue; } @@ -271,7 +271,7 @@ namespace StardewModdingAPI.Web.Controllers } // get version info - return this.ModSites.GetPageVersions(page, allowNonStandardVersions, mapRemoteVersions); + return this.ModSites.GetPageVersions(page, updateKey.Subkey, allowNonStandardVersions, mapRemoteVersions); } /// Get update keys based on the available mod metadata, while maintaining the precedence order. @@ -280,44 +280,56 @@ namespace StardewModdingAPI.Web.Controllers /// The mod's entry in the wiki list. private IEnumerable GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { + // get every update key (including duplicates) IEnumerable GetRaw() { // specified update keys if (specifiedKeys != null) { foreach (string key in specifiedKeys) - yield return key?.Trim(); + { + if (!string.IsNullOrWhiteSpace(key)) + yield return key.Trim(); + } } // default update key string defaultKey = record?.GetDefaultUpdateKey(); - if (defaultKey != null) + if (!string.IsNullOrWhiteSpace(defaultKey)) yield return defaultKey; // wiki metadata if (entry != null) { if (entry.NexusID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID?.ToString()); + yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID.ToString()); if (entry.ModDropID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID?.ToString()); + yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID.ToString()); if (entry.CurseForgeID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID?.ToString()); + yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID.ToString()); if (entry.ChucklefishID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID?.ToString()); + yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID.ToString()); } } - HashSet seen = new HashSet(); - foreach (string rawKey in GetRaw()) - { - if (string.IsNullOrWhiteSpace(rawKey)) - continue; - - UpdateKey key = UpdateKey.Parse(rawKey); - if (seen.Add(key)) - yield return key; - } + // get unique update keys + var subkeyRoots = new HashSet(); + List updateKeys = GetRaw() + .Select(raw => + { + var key = UpdateKey.Parse(raw); + if (key.Subkey != null) + subkeyRoots.Add(new UpdateKey(key.Site, key.ID, null)); + return key; + }) + .Distinct() + .ToList(); + + // if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority + if (subkeyRoots.Any()) + updateKeys.RemoveAll(subkeyRoots.Contains); + + return updateKeys; } } } -- cgit From d9c2d242b9457a6517ec348c945ae1a324582492 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 24 May 2020 16:39:56 -0400 Subject: add update key overrides --- src/SMAPI.Web/Controllers/ModsApiController.cs | 103 +++++++++++++++---------- 1 file changed, 63 insertions(+), 40 deletions(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 028fc613..db669bf9 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -280,56 +280,79 @@ namespace StardewModdingAPI.Web.Controllers /// The mod's entry in the wiki list. private IEnumerable GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { - // get every update key (including duplicates) - IEnumerable GetRaw() + // get unique update keys + List updateKeys = this.GetUnfilteredUpdateKeys(specifiedKeys, record, entry) + .Select(UpdateKey.Parse) + .Distinct() + .ToList(); + + // apply remove overrides from wiki + { + var removeKeys = new HashSet( + from key in entry?.ChangeUpdateKeys ?? new string[0] + where key.StartsWith('-') + select UpdateKey.Parse(key.Substring(1)) + ); + if (removeKeys.Any()) + updateKeys.RemoveAll(removeKeys.Contains); + } + + // if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority { - // specified update keys - if (specifiedKeys != null) + var removeKeys = new HashSet(); + foreach (var key in updateKeys) { - foreach (string key in specifiedKeys) - { - if (!string.IsNullOrWhiteSpace(key)) - yield return key.Trim(); - } + if (key.Subkey != null) + removeKeys.Add(new UpdateKey(key.Site, key.ID, null)); } + if (removeKeys.Any()) + updateKeys.RemoveAll(removeKeys.Contains); + } + + return updateKeys; + } + + /// Get every available update key based on the available mod metadata, including duplicates and keys which should be filtered. + /// The specified update keys. + /// The mod's entry in SMAPI's internal database. + /// The mod's entry in the wiki list. + private IEnumerable GetUnfilteredUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) + { + // specified update keys + foreach (string key in specifiedKeys ?? Array.Empty()) + { + if (!string.IsNullOrWhiteSpace(key)) + yield return key.Trim(); + } - // default update key + // default update key + { string defaultKey = record?.GetDefaultUpdateKey(); if (!string.IsNullOrWhiteSpace(defaultKey)) yield return defaultKey; - - // wiki metadata - if (entry != null) - { - if (entry.NexusID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID.ToString()); - if (entry.ModDropID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID.ToString()); - if (entry.CurseForgeID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID.ToString()); - if (entry.ChucklefishID.HasValue) - yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID.ToString()); - } } - // get unique update keys - var subkeyRoots = new HashSet(); - List updateKeys = GetRaw() - .Select(raw => - { - var key = UpdateKey.Parse(raw); - if (key.Subkey != null) - subkeyRoots.Add(new UpdateKey(key.Site, key.ID, null)); - return key; - }) - .Distinct() - .ToList(); - - // if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority - if (subkeyRoots.Any()) - updateKeys.RemoveAll(subkeyRoots.Contains); + // wiki metadata + if (entry != null) + { + if (entry.NexusID.HasValue) + yield return UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID.ToString()); + if (entry.ModDropID.HasValue) + yield return UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID.ToString()); + if (entry.CurseForgeID.HasValue) + yield return UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID.ToString()); + if (entry.ChucklefishID.HasValue) + yield return UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID.ToString()); + } - return updateKeys; + // overrides from wiki + foreach (string key in entry?.ChangeUpdateKeys ?? Array.Empty()) + { + if (key.StartsWith('+')) + yield return key.Substring(1); + else if (!key.StartsWith("-")) + yield return key; + } } } } -- cgit From b32cad4046916344665c67923482db09cacb366f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Jun 2020 11:13:23 -0400 Subject: add i18n schema to JSON validator --- src/SMAPI.Web/Controllers/JsonValidatorController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index c43fb929..8fb9e06a 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -27,7 +27,8 @@ namespace StardewModdingAPI.Web.Controllers private readonly IDictionary SchemaFormats = new Dictionary { ["none"] = "None", - ["manifest"] = "Manifest", + ["manifest"] = "SMAPI: manifest", + ["i18n"] = "SMAPI: translations (i18n)", ["content-patcher"] = "Content Patcher" }; -- cgit From d02a40de997edf493354e85eb018ded84eb8f782 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Jun 2020 11:31:01 -0400 Subject: change default JSON validator schema to none --- src/SMAPI.Web/Controllers/JsonValidatorController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index 8fb9e06a..b76d41a3 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI.Web.Controllers }; /// The schema ID to use if none was specified. - private string DefaultSchemaID = "manifest"; + private string DefaultSchemaID = "none"; /// A token in an error message which indicates that the child errors should be displayed instead. private readonly string TransparentToken = "$transparent"; -- cgit From ed3309e7bb8d5f3f6c3d08df3475bd811d5b16d0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Jun 2020 11:36:22 -0400 Subject: remember selected schema when editing a file --- .../Controllers/JsonValidatorController.cs | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'src/SMAPI.Web/Controllers') diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index b76d41a3..5f83eafd 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -58,16 +58,22 @@ namespace StardewModdingAPI.Web.Controllers /// Render the schema validator UI. /// The schema name with which to validate the JSON, or 'edit' to return to the edit screen. /// The stored file ID. + /// The operation to perform for the selected log ID. This can be 'edit', or any other value to view. [HttpGet] [Route("json")] [Route("json/{schemaName}")] [Route("json/{schemaName}/{id}")] - public async Task Index(string schemaName = null, string id = null) + [Route("json/{schemaName}/{id}/{operation}")] + public async Task Index(string schemaName = null, string id = null, string operation = null) { + // parse arguments schemaName = this.NormalizeSchemaName(schemaName); + bool hasId = !string.IsNullOrWhiteSpace(id); + bool isEditView = !hasId || operation?.Trim().ToLower() == "edit"; - var result = new JsonValidatorModel(id, schemaName, this.SchemaFormats); - if (string.IsNullOrWhiteSpace(id)) + // build result model + var result = this.GetModel(id, schemaName, isEditView); + if (!hasId) return this.View("Index", result); // fetch raw JSON @@ -77,7 +83,7 @@ namespace StardewModdingAPI.Web.Controllers result.SetContent(file.Content, expiry: file.Expiry, uploadWarning: file.Warning); // skip parsing if we're going to the edit screen - if (schemaName?.ToLower() == "edit") + if (isEditView) return this.View("Index", result); // parse JSON @@ -131,7 +137,7 @@ namespace StardewModdingAPI.Web.Controllers public async Task PostAsync(JsonValidatorRequestModel request) { if (request == null) - return this.View("Index", this.GetModel(null, null).SetUploadError("The request seems to be invalid.")); + return this.View("Index", this.GetModel(null, null, isEditView: true).SetUploadError("The request seems to be invalid.")); // normalize schema name string schemaName = this.NormalizeSchemaName(request.SchemaName); @@ -139,12 +145,12 @@ namespace StardewModdingAPI.Web.Controllers // get raw text string input = request.Content; if (string.IsNullOrWhiteSpace(input)) - return this.View("Index", this.GetModel(null, schemaName).SetUploadError("The JSON file seems to be empty.")); + return this.View("Index", this.GetModel(null, schemaName, isEditView: true).SetUploadError("The JSON file seems to be empty.")); // upload file UploadResult result = await this.Storage.SaveAsync(input); if (!result.Succeeded) - return this.View("Index", this.GetModel(result.ID, schemaName).SetUploadError(result.UploadError)); + return this.View("Index", this.GetModel(result.ID, schemaName, isEditView: true).SetContent(input, null).SetUploadError(result.UploadError)); // redirect to view return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName = schemaName, id = result.ID })); @@ -157,9 +163,10 @@ namespace StardewModdingAPI.Web.Controllers /// Build a JSON validator model. /// The stored file ID. /// The schema name with which the JSON was validated. - private JsonValidatorModel GetModel(string pasteID, string schemaName) + /// Whether to show the edit view. + private JsonValidatorModel GetModel(string pasteID, string schemaName, bool isEditView) { - return new JsonValidatorModel(pasteID, schemaName, this.SchemaFormats); + return new JsonValidatorModel(pasteID, schemaName, this.SchemaFormats, isEditView); } /// Get a normalized schema name, or the if blank. -- cgit