From a500812e88ac5a8acdd9732963e6fae95c0a73e6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 7 May 2020 22:41:37 -0400 Subject: update web project to .NET Core 3.1 --- src/SMAPI.Web/Framework/Extensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Extensions.cs b/src/SMAPI.Web/Framework/Extensions.cs index e0da1424..96463233 100644 --- a/src/SMAPI.Web/Framework/Extensions.cs +++ b/src/SMAPI.Web/Framework/Extensions.cs @@ -1,8 +1,12 @@ using System; using JetBrains.Annotations; +using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework { @@ -34,5 +38,16 @@ namespace StardewModdingAPI.Web.Framework } return url; } + + /// Get a serialized JSON representation of the value. + /// The page to extend. + /// The value to serialize. + /// The serialized JSON. + /// This bypasses unnecessary validation (e.g. not allowing null values) in . + public static IHtmlContent ForJson(this RazorPageBase page, object value) + { + string json = JsonConvert.SerializeObject(value); + return new HtmlString(json); + } } } -- cgit From 896f531f4f6fc7f0e8dfcfc0d6433850233ee749 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 14 May 2020 19:25:51 -0400 Subject: fix broken action links after update to .NET Core 3.0 --- src/SMAPI.Web/Framework/Extensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Extensions.cs b/src/SMAPI.Web/Framework/Extensions.cs index 96463233..ad7e645a 100644 --- a/src/SMAPI.Web/Framework/Extensions.cs +++ b/src/SMAPI.Web/Framework/Extensions.cs @@ -22,6 +22,7 @@ namespace StardewModdingAPI.Web.Framework /// The generated URL. public static string PlainAction(this IUrlHelper helper, [AspMvcAction] string action, [AspMvcController] string controller, object values = null, bool absoluteUrl = false) { + // get route values RouteValueDictionary valuesDict = new RouteValueDictionary(values); foreach (var value in helper.ActionContext.RouteData.Values) { @@ -29,13 +30,19 @@ namespace StardewModdingAPI.Web.Framework valuesDict[value.Key] = null; // explicitly remove it from the URL } + // get relative URL string url = helper.Action(action, controller, valuesDict); + if (url == null && action.EndsWith("Async")) + url = helper.Action(action[..^"Async".Length], controller, valuesDict); + + // get absolute URL if (absoluteUrl) { HttpRequest request = helper.ActionContext.HttpContext.Request; Uri baseUri = new Uri($"{request.Scheme}://{request.Host}"); url = new Uri(baseUri, url).ToString(); } + return url; } -- cgit 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 --- src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs | 3 +-- src/SMAPI.Web/Framework/Compression/GzipHelper.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs b/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs index 140b854e..a6fd21fd 100644 --- a/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs +++ b/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs @@ -57,8 +57,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.CurseForge if (!SemanticVersion.TryParse(raw, out version)) { - if (invalidVersion == null) - invalidVersion = raw; + invalidVersion ??= raw; continue; } } diff --git a/src/SMAPI.Web/Framework/Compression/GzipHelper.cs b/src/SMAPI.Web/Framework/Compression/GzipHelper.cs index cc8f4737..676d660d 100644 --- a/src/SMAPI.Web/Framework/Compression/GzipHelper.cs +++ b/src/SMAPI.Web/Framework/Compression/GzipHelper.cs @@ -69,7 +69,7 @@ namespace StardewModdingAPI.Web.Framework.Compression return rawText; // decompress - using (MemoryStream memoryStream = new MemoryStream()) + using MemoryStream memoryStream = new MemoryStream(); { // read length prefix int dataLength = BitConverter.ToInt32(zipBuffer, 0); -- 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/Framework/Caching/Mods/ModCacheRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs index 2e7804a7..d6906866 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs @@ -88,7 +88,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods this.Mods.ReplaceOne( entry => entry.ID == id && entry.Site == mod.Site, mod, - new UpdateOptions { IsUpsert = true } + new ReplaceOptions { IsUpsert = true } ); return mod; -- cgit From a2cfb71d898aca98d621f1b86dd5611337eea034 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 May 2020 11:34:00 -0400 Subject: minor cleanup --- src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs | 2 +- src/SMAPI.Web/Framework/LogParsing/LogParser.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs index d6906866..6ba1d73d 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs @@ -72,7 +72,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods public void RemoveStaleMods(TimeSpan age) { DateTimeOffset minDate = DateTimeOffset.UtcNow.Subtract(age); - var result = this.Mods.DeleteMany(p => p.LastRequested < minDate); + this.Mods.DeleteMany(p => p.LastRequested < minDate); } diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index cce80816..227dcd89 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -45,7 +45,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing private readonly Regex ModUpdateListEntryPattern = new Regex(@"^ (?.+?) (?[^\s]+): (?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// A regex pattern matching SMAPI's update line. - private readonly Regex SMAPIUpdatePattern = new Regex(@"^You can update SMAPI to (?[^\s]+): (?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex SmapiUpdatePattern = new Regex(@"^You can update SMAPI to (?[^\s]+): (?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /********* @@ -181,9 +181,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing message.Section = LogSection.ModUpdateList; } - else if (message.Level == LogLevel.Alert && this.SMAPIUpdatePattern.IsMatch(message.Text)) + else if (message.Level == LogLevel.Alert && this.SmapiUpdatePattern.IsMatch(message.Text)) { - Match match = this.SMAPIUpdatePattern.Match(message.Text); + Match match = this.SmapiUpdatePattern.Match(message.Text); string version = match.Groups["version"].Value; string link = match.Groups["link"].Value; smapiMod.UpdateVersion = version; -- cgit From 5e6f1640dcb8e30a44f8ff07572874850b12cc2e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 May 2020 14:30:07 -0400 Subject: simplify single-instance deployment and make MongoDB server optional --- .../Framework/Caching/Mods/IModCacheRepository.cs | 2 +- .../Caching/Mods/ModCacheMemoryRepository.cs | 89 +++++++++++++++++ .../Caching/Mods/ModCacheMongoRepository.cs | 105 +++++++++++++++++++++ .../Framework/Caching/Mods/ModCacheRepository.cs | 104 -------------------- .../Framework/Caching/Wiki/IWikiCacheRepository.cs | 2 +- .../Caching/Wiki/WikiCacheMemoryRepository.cs | 54 +++++++++++ .../Caching/Wiki/WikiCacheMongoRepository.cs | 73 ++++++++++++++ .../Framework/Caching/Wiki/WikiCacheRepository.cs | 73 -------------- .../Framework/ConfigModels/MongoDbConfig.cs | 25 ----- .../Framework/ConfigModels/StorageConfig.cs | 18 ++++ .../Framework/ConfigModels/StorageMode.cs | 15 +++ 11 files changed, 356 insertions(+), 204 deletions(-) create mode 100644 src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Mods/ModCacheMongoRepository.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs delete mode 100644 src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs create mode 100644 src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs create mode 100644 src/SMAPI.Web/Framework/ConfigModels/StorageMode.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs index bcec8b36..08749f3b 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs @@ -4,7 +4,7 @@ using StardewModdingAPI.Web.Framework.ModRepositories; namespace StardewModdingAPI.Web.Framework.Caching.Mods { - /// Encapsulates logic for accessing the mod data cache. + /// Manages cached mod data. internal interface IModCacheRepository : ICacheRepository { /********* diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs new file mode 100644 index 00000000..9c5a217e --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Toolkit.Framework.UpdateData; +using StardewModdingAPI.Web.Framework.ModRepositories; + +namespace StardewModdingAPI.Web.Framework.Caching.Mods +{ + /// Manages cached mod data in-memory. + internal class ModCacheMemoryRepository : BaseCacheRepository, IModCacheRepository + { + /********* + ** Fields + *********/ + /// The cached mod data indexed by {site key}:{ID}. + private readonly IDictionary Mods = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + + /********* + ** Public methods + *********/ + /// Get the cached mod data. + /// The mod site to search. + /// The mod's unique ID within the . + /// The fetched mod. + /// Whether to update the mod's 'last requested' date. + public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true) + { + // get mod + if (!this.Mods.TryGetValue(this.GetKey(site, id), out mod)) + return false; + + // bump 'last requested' + if (markRequested) + { + mod.LastRequested = DateTimeOffset.UtcNow; + mod = this.SaveMod(mod); + } + + return true; + } + + /// Save data fetched for a mod. + /// The mod site on which the mod is found. + /// The mod's unique ID within the . + /// The mod data. + /// The stored mod record. + public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod) + { + string key = this.GetKey(site, id); + cachedMod = this.SaveMod(new CachedMod(site, id, mod)); + } + + /// Delete data for mods which haven't been requested within a given time limit. + /// The minimum age for which to remove mods. + public void RemoveStaleMods(TimeSpan age) + { + DateTimeOffset minDate = DateTimeOffset.UtcNow.Subtract(age); + + string[] staleKeys = this.Mods + .Where(p => p.Value.LastRequested < minDate) + .Select(p => p.Key) + .ToArray(); + + foreach (string key in staleKeys) + this.Mods.Remove(key); + } + + /// Save data fetched for a mod. + /// The mod data. + public CachedMod SaveMod(CachedMod mod) + { + string key = this.GetKey(mod.Site, mod.ID); + return this.Mods[key] = mod; + } + + + /********* + ** Private methods + *********/ + /// Get a cache key. + /// The mod site. + /// The mod ID. + public string GetKey(ModRepositoryKey site, string id) + { + return $"{site}:{id.Trim()}".ToLower(); + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMongoRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMongoRepository.cs new file mode 100644 index 00000000..f105baab --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMongoRepository.cs @@ -0,0 +1,105 @@ +using System; +using MongoDB.Driver; +using StardewModdingAPI.Toolkit.Framework.UpdateData; +using StardewModdingAPI.Web.Framework.ModRepositories; + +namespace StardewModdingAPI.Web.Framework.Caching.Mods +{ + /// Manages cached mod data in MongoDB. + internal class ModCacheMongoRepository : BaseCacheRepository, IModCacheRepository + { + /********* + ** Fields + *********/ + /// The collection for cached mod data. + private readonly IMongoCollection Mods; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The authenticated MongoDB database. + public ModCacheMongoRepository(IMongoDatabase database) + { + // get collections + this.Mods = database.GetCollection("mods"); + + // add indexes if needed + this.Mods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(p => p.ID).Ascending(p => p.Site))); + } + + + /********* + ** Public methods + *********/ + /// Get the cached mod data. + /// The mod site to search. + /// The mod's unique ID within the . + /// The fetched mod. + /// Whether to update the mod's 'last requested' date. + public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true) + { + // get mod + id = this.NormalizeId(id); + mod = this.Mods.Find(entry => entry.ID == id && entry.Site == site).FirstOrDefault(); + if (mod == null) + return false; + + // bump 'last requested' + if (markRequested) + { + mod.LastRequested = DateTimeOffset.UtcNow; + mod = this.SaveMod(mod); + } + + return true; + } + + /// Save data fetched for a mod. + /// The mod site on which the mod is found. + /// The mod's unique ID within the . + /// The mod data. + /// The stored mod record. + public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod) + { + id = this.NormalizeId(id); + + cachedMod = this.SaveMod(new CachedMod(site, id, mod)); + } + + /// Delete data for mods which haven't been requested within a given time limit. + /// The minimum age for which to remove mods. + public void RemoveStaleMods(TimeSpan age) + { + DateTimeOffset minDate = DateTimeOffset.UtcNow.Subtract(age); + this.Mods.DeleteMany(p => p.LastRequested < minDate); + } + + /// Save data fetched for a mod. + /// The mod data. + public CachedMod SaveMod(CachedMod mod) + { + string id = this.NormalizeId(mod.ID); + + this.Mods.ReplaceOne( + entry => entry.ID == id && entry.Site == mod.Site, + mod, + new ReplaceOptions { IsUpsert = true } + ); + + return mod; + } + + + /********* + ** Private methods + *********/ + /// Normalize a mod ID for case-insensitive search. + /// The mod ID. + public string NormalizeId(string id) + { + return id.Trim().ToLower(); + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs deleted file mode 100644 index 6ba1d73d..00000000 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using MongoDB.Driver; -using StardewModdingAPI.Toolkit.Framework.UpdateData; -using StardewModdingAPI.Web.Framework.ModRepositories; - -namespace StardewModdingAPI.Web.Framework.Caching.Mods -{ - /// Encapsulates logic for accessing the mod data cache. - internal class ModCacheRepository : BaseCacheRepository, IModCacheRepository - { - /********* - ** Fields - *********/ - /// The collection for cached mod data. - private readonly IMongoCollection Mods; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The authenticated MongoDB database. - public ModCacheRepository(IMongoDatabase database) - { - // get collections - this.Mods = database.GetCollection("mods"); - - // add indexes if needed - this.Mods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(p => p.ID).Ascending(p => p.Site))); - } - - /********* - ** Public methods - *********/ - /// Get the cached mod data. - /// The mod site to search. - /// The mod's unique ID within the . - /// The fetched mod. - /// Whether to update the mod's 'last requested' date. - public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true) - { - // get mod - id = this.NormalizeId(id); - mod = this.Mods.Find(entry => entry.ID == id && entry.Site == site).FirstOrDefault(); - if (mod == null) - return false; - - // bump 'last requested' - if (markRequested) - { - mod.LastRequested = DateTimeOffset.UtcNow; - mod = this.SaveMod(mod); - } - - return true; - } - - /// Save data fetched for a mod. - /// The mod site on which the mod is found. - /// The mod's unique ID within the . - /// The mod data. - /// The stored mod record. - public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod) - { - id = this.NormalizeId(id); - - cachedMod = this.SaveMod(new CachedMod(site, id, mod)); - } - - /// Delete data for mods which haven't been requested within a given time limit. - /// The minimum age for which to remove mods. - public void RemoveStaleMods(TimeSpan age) - { - DateTimeOffset minDate = DateTimeOffset.UtcNow.Subtract(age); - this.Mods.DeleteMany(p => p.LastRequested < minDate); - } - - - /********* - ** Private methods - *********/ - /// Save data fetched for a mod. - /// The mod data. - public CachedMod SaveMod(CachedMod mod) - { - string id = this.NormalizeId(mod.ID); - - this.Mods.ReplaceOne( - entry => entry.ID == id && entry.Site == mod.Site, - mod, - new ReplaceOptions { IsUpsert = true } - ); - - return mod; - } - - /// Normalize a mod ID for case-insensitive search. - /// The mod ID. - public string NormalizeId(string id) - { - return id.Trim().ToLower(); - } - } -} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs index b54c8a2f..02097f52 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs @@ -5,7 +5,7 @@ using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; namespace StardewModdingAPI.Web.Framework.Caching.Wiki { - /// Encapsulates logic for accessing the wiki data cache. + /// Manages cached wiki data. internal interface IWikiCacheRepository : ICacheRepository { /********* diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs new file mode 100644 index 00000000..4621f5e3 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// Manages cached wiki data in-memory. + internal class WikiCacheMemoryRepository : BaseCacheRepository, IWikiCacheRepository + { + /********* + ** Fields + *********/ + /// The saved wiki metadata. + private CachedWikiMetadata Metadata; + + /// The cached wiki data. + private CachedWikiMod[] Mods = new CachedWikiMod[0]; + + + /********* + ** Public methods + *********/ + /// Get the cached wiki metadata. + /// The fetched metadata. + public bool TryGetWikiMetadata(out CachedWikiMetadata metadata) + { + metadata = this.Metadata; + return metadata != null; + } + + /// Get the cached wiki mods. + /// A filter to apply, if any. + public IEnumerable GetWikiMods(Expression> filter = null) + { + return filter != null + ? this.Mods.Where(filter.Compile()) + : this.Mods.ToArray(); + } + + /// Save data fetched from the wiki compatibility list. + /// The current stable Stardew Valley version. + /// The current beta Stardew Valley version. + /// The mod data. + /// The stored metadata record. + /// The stored mod records. + public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods) + { + this.Metadata = cachedMetadata = new CachedWikiMetadata(stableVersion, betaVersion); + this.Mods = cachedMods = mods.Select(mod => new CachedWikiMod(mod)).ToArray(); + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs new file mode 100644 index 00000000..07e7c721 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using MongoDB.Driver; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// Manages cached wiki data in MongoDB. + internal class WikiCacheMongoRepository : BaseCacheRepository, IWikiCacheRepository + { + /********* + ** Fields + *********/ + /// The collection for wiki metadata. + private readonly IMongoCollection Metadata; + + /// The collection for wiki mod data. + private readonly IMongoCollection Mods; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The authenticated MongoDB database. + public WikiCacheMongoRepository(IMongoDatabase database) + { + // get collections + this.Metadata = database.GetCollection("wiki-metadata"); + this.Mods = database.GetCollection("wiki-mods"); + + // add indexes if needed + this.Mods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(p => p.ID))); + } + + /// Get the cached wiki metadata. + /// The fetched metadata. + public bool TryGetWikiMetadata(out CachedWikiMetadata metadata) + { + metadata = this.Metadata.Find("{}").FirstOrDefault(); + return metadata != null; + } + + /// Get the cached wiki mods. + /// A filter to apply, if any. + public IEnumerable GetWikiMods(Expression> filter = null) + { + return filter != null + ? this.Mods.Find(filter).ToList() + : this.Mods.Find("{}").ToList(); + } + + /// Save data fetched from the wiki compatibility list. + /// The current stable Stardew Valley version. + /// The current beta Stardew Valley version. + /// The mod data. + /// The stored metadata record. + /// The stored mod records. + public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods) + { + cachedMetadata = new CachedWikiMetadata(stableVersion, betaVersion); + cachedMods = mods.Select(mod => new CachedWikiMod(mod)).ToArray(); + + this.Mods.DeleteMany("{}"); + this.Mods.InsertMany(cachedMods); + + this.Metadata.DeleteMany("{}"); + this.Metadata.InsertOne(cachedMetadata); + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs deleted file mode 100644 index 1ae9d38f..00000000 --- a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using MongoDB.Driver; -using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; - -namespace StardewModdingAPI.Web.Framework.Caching.Wiki -{ - /// Encapsulates logic for accessing the wiki data cache. - internal class WikiCacheRepository : BaseCacheRepository, IWikiCacheRepository - { - /********* - ** Fields - *********/ - /// The collection for wiki metadata. - private readonly IMongoCollection WikiMetadata; - - /// The collection for wiki mod data. - private readonly IMongoCollection WikiMods; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The authenticated MongoDB database. - public WikiCacheRepository(IMongoDatabase database) - { - // get collections - this.WikiMetadata = database.GetCollection("wiki-metadata"); - this.WikiMods = database.GetCollection("wiki-mods"); - - // add indexes if needed - this.WikiMods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(p => p.ID))); - } - - /// Get the cached wiki metadata. - /// The fetched metadata. - public bool TryGetWikiMetadata(out CachedWikiMetadata metadata) - { - metadata = this.WikiMetadata.Find("{}").FirstOrDefault(); - return metadata != null; - } - - /// Get the cached wiki mods. - /// A filter to apply, if any. - public IEnumerable GetWikiMods(Expression> filter = null) - { - return filter != null - ? this.WikiMods.Find(filter).ToList() - : this.WikiMods.Find("{}").ToList(); - } - - /// Save data fetched from the wiki compatibility list. - /// The current stable Stardew Valley version. - /// The current beta Stardew Valley version. - /// The mod data. - /// The stored metadata record. - /// The stored mod records. - public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods) - { - cachedMetadata = new CachedWikiMetadata(stableVersion, betaVersion); - cachedMods = mods.Select(mod => new CachedWikiMod(mod)).ToArray(); - - this.WikiMods.DeleteMany("{}"); - this.WikiMods.InsertMany(cachedMods); - - this.WikiMetadata.DeleteMany("{}"); - this.WikiMetadata.InsertOne(cachedMetadata); - } - } -} diff --git a/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs deleted file mode 100644 index c7b6cb00..00000000 --- a/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace StardewModdingAPI.Web.Framework.ConfigModels -{ - /// The config settings for mod compatibility list. - internal class MongoDbConfig - { - /********* - ** Accessors - *********/ - /// The MongoDB connection string. - public string ConnectionString { get; set; } - - /// The database name. - public string Database { get; set; } - - - /********* - ** Public method - *********/ - /// Get whether a MongoDB instance is configured. - public bool IsConfigured() - { - return !string.IsNullOrWhiteSpace(this.ConnectionString); - } - } -} diff --git a/src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs new file mode 100644 index 00000000..61cc4855 --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// The config settings for cache storage. + internal class StorageConfig + { + /********* + ** Accessors + *********/ + /// The storage mechanism to use. + public StorageMode Mode { get; set; } + + /// The connection string for the storage mechanism, if applicable. + public string ConnectionString { get; set; } + + /// The database name for the storage mechanism, if applicable. + public string Database { get; set; } + } +} diff --git a/src/SMAPI.Web/Framework/ConfigModels/StorageMode.cs b/src/SMAPI.Web/Framework/ConfigModels/StorageMode.cs new file mode 100644 index 00000000..4c2ea801 --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/StorageMode.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// Indicates a storage mechanism to use. + internal enum StorageMode + { + /// Store data in a hosted MongoDB instance. + Mongo, + + /// Store data in an in-memory MongoDB instance. This is useful for testing MongoDB storage locally, but will likely fail when deployed since it needs permission to open a local port. + MongoInMemory, + + /// Store data in-memory. This is suitable for local testing or single-instance servers, but will cause issues when distributed across multiple servers. + InMemory + } +} -- cgit From 9d86f20ca728811c1da908337a4d5e7a998e5b48 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 May 2020 20:01:52 -0400 Subject: migrate subdomain redirects to Azure --- .../RedirectRules/RedirectHostsToUrlsRule.cs | 54 +++++++++++++++++++ .../Framework/RedirectRules/RedirectMatchRule.cs | 58 ++++++++++++++++++++ .../RedirectRules/RedirectPathsToUrlsRule.cs | 54 +++++++++++++++++++ .../Framework/RedirectRules/RedirectToHttpsRule.cs | 47 ++++++++++++++++ .../RewriteRules/ConditionalRedirectToHttpsRule.cs | 62 ---------------------- .../Framework/RewriteRules/RedirectToUrlRule.cs | 57 -------------------- 6 files changed, 213 insertions(+), 119 deletions(-) create mode 100644 src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs create mode 100644 src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs create mode 100644 src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs create mode 100644 src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs delete mode 100644 src/SMAPI.Web/Framework/RewriteRules/ConditionalRedirectToHttpsRule.cs delete mode 100644 src/SMAPI.Web/Framework/RewriteRules/RedirectToUrlRule.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs new file mode 100644 index 00000000..d75ee791 --- /dev/null +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs @@ -0,0 +1,54 @@ +using System; +using System.Net; +using Microsoft.AspNetCore.Rewrite; + +namespace StardewModdingAPI.Web.Framework.RedirectRules +{ + /// Redirect hostnames to a URL if they match a condition. + internal class RedirectHostsToUrlsRule : RedirectMatchRule + { + /********* + ** Fields + *********/ + /// Maps a lowercase hostname to the resulting redirect URL. + private readonly Func Map; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The status code to use for redirects. + /// Hostnames mapped to the resulting redirect URL. + public RedirectHostsToUrlsRule(HttpStatusCode statusCode, Func map) + { + this.StatusCode = statusCode; + this.Map = map ?? throw new ArgumentNullException(nameof(map)); + } + + + /********* + ** Private methods + *********/ + /// Get the new redirect URL. + /// The rewrite context. + /// Returns the redirect URL, or null if the redirect doesn't apply. + protected override string GetNewUrl(RewriteContext context) + { + // get requested host + string host = context.HttpContext.Request.Host.Host; + if (host == null) + return null; + + // get new host + host = this.Map(host); + if (host == null) + return null; + + // rewrite URL + UriBuilder uri = this.GetUrl(context.HttpContext.Request); + uri.Host = host; + return uri.ToString(); + } + } +} diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs new file mode 100644 index 00000000..6e81c4ca --- /dev/null +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs @@ -0,0 +1,58 @@ +using System; +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite; + +namespace StardewModdingAPI.Web.Framework.RedirectRules +{ + /// Redirect matching requests to a URL. + internal abstract class RedirectMatchRule : IRule + { + /********* + ** Fields + *********/ + /// The status code to use for redirects. + protected HttpStatusCode StatusCode { get; set; } = HttpStatusCode.Redirect; + + + /********* + ** Public methods + *********/ + /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). + /// The rewrite context. + public void ApplyRule(RewriteContext context) + { + string newUrl = this.GetNewUrl(context); + if (newUrl == null) + return; + + HttpResponse response = context.HttpContext.Response; + response.StatusCode = (int)HttpStatusCode.Redirect; + response.Headers["Location"] = newUrl; + context.Result = RuleResult.EndResponse; + } + + + /********* + ** Protected methods + *********/ + /// Get the new redirect URL. + /// The rewrite context. + /// Returns the redirect URL, or null if the redirect doesn't apply. + protected abstract string GetNewUrl(RewriteContext context); + + /// Get the full request URL. + /// The request. + protected UriBuilder GetUrl(HttpRequest request) + { + return new UriBuilder + { + Scheme = request.Scheme, + Host = request.Host.Host, + Port = request.Host.Port ?? -1, + Path = request.PathBase + request.Path, + Query = request.QueryString.Value + }; + } + } +} diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs new file mode 100644 index 00000000..16397c1e --- /dev/null +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Rewrite; + +namespace StardewModdingAPI.Web.Framework.RedirectRules +{ + /// Redirect paths to URLs if they match a condition. + internal class RedirectPathsToUrlsRule : RedirectMatchRule + { + /********* + ** Fields + *********/ + /// Regex patterns matching the current URL mapped to the resulting redirect URL. + private readonly IDictionary Map; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Regex patterns matching the current URL mapped to the resulting redirect URL. + public RedirectPathsToUrlsRule(IDictionary map) + { + this.Map = map.ToDictionary( + p => new Regex(p.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled), + p => p.Value + ); + } + + + /********* + ** Protected methods + *********/ + /// Get the new redirect URL. + /// The rewrite context. + /// Returns the redirect URL, or null if the redirect doesn't apply. + protected override string GetNewUrl(RewriteContext context) + { + string path = context.HttpContext.Request.Path.Value; + + if (!string.IsNullOrWhiteSpace(path)) + { + foreach ((Regex pattern, string url) in this.Map) + { + if (pattern.IsMatch(path)) + return pattern.Replace(path, url); + } + } + + return null; + } + } +} diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs new file mode 100644 index 00000000..2a503ae3 --- /dev/null +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs @@ -0,0 +1,47 @@ +using System; +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite; + +namespace StardewModdingAPI.Web.Framework.RedirectRules +{ + /// Redirect requests to HTTPS. + internal class RedirectToHttpsRule : RedirectMatchRule + { + /********* + ** Fields + *********/ + /// Matches requests which should be ignored. + private readonly Func Except; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Matches requests which should be ignored. + public RedirectToHttpsRule(Func except = null) + { + this.Except = except ?? (req => false); + this.StatusCode = HttpStatusCode.RedirectKeepVerb; + } + + + /********* + ** Protected methods + *********/ + /// Get the new redirect URL. + /// The rewrite context. + /// Returns the redirect URL, or null if the redirect doesn't apply. + protected override string GetNewUrl(RewriteContext context) + { + HttpRequest request = context.HttpContext.Request; + if (request.IsHttps || this.Except(request)) + return null; + + UriBuilder uri = this.GetUrl(request); + uri.Scheme = "https"; + return uri.ToString(); + } + } +} diff --git a/src/SMAPI.Web/Framework/RewriteRules/ConditionalRedirectToHttpsRule.cs b/src/SMAPI.Web/Framework/RewriteRules/ConditionalRedirectToHttpsRule.cs deleted file mode 100644 index 36effd82..00000000 --- a/src/SMAPI.Web/Framework/RewriteRules/ConditionalRedirectToHttpsRule.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Net; -using System.Text; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite; - -namespace StardewModdingAPI.Web.Framework.RewriteRules -{ - /// Redirect requests to HTTPS. - /// Derived from and . - internal class ConditionalRedirectToHttpsRule : IRule - { - /********* - ** Fields - *********/ - /// A predicate which indicates when the rule should be applied. - private readonly Func ShouldRewrite; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// A predicate which indicates when the rule should be applied. - public ConditionalRedirectToHttpsRule(Func shouldRewrite = null) - { - this.ShouldRewrite = shouldRewrite ?? (req => true); - } - - /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). - /// The rewrite context. - public void ApplyRule(RewriteContext context) - { - HttpRequest request = context.HttpContext.Request; - - // check condition - if (this.IsSecure(request) || !this.ShouldRewrite(request)) - return; - - // redirect request - HttpResponse response = context.HttpContext.Response; - response.StatusCode = (int)HttpStatusCode.RedirectKeepVerb; - response.Headers["Location"] = new StringBuilder() - .Append("https://") - .Append(request.Host.Host) - .Append(request.PathBase) - .Append(request.Path) - .Append(request.QueryString) - .ToString(); - context.Result = RuleResult.EndResponse; - } - - /// Get whether the request was received over HTTPS. - /// The request to check. - public bool IsSecure(HttpRequest request) - { - return - request.IsHttps // HTTPS to server - || string.Equals(request.Headers["x-forwarded-proto"], "HTTPS", StringComparison.OrdinalIgnoreCase); // HTTPS to AWS load balancer - } - } -} diff --git a/src/SMAPI.Web/Framework/RewriteRules/RedirectToUrlRule.cs b/src/SMAPI.Web/Framework/RewriteRules/RedirectToUrlRule.cs deleted file mode 100644 index ab9e019c..00000000 --- a/src/SMAPI.Web/Framework/RewriteRules/RedirectToUrlRule.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Net; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite; - -namespace StardewModdingAPI.Web.Framework.RewriteRules -{ - /// Redirect requests to an external URL if they match a condition. - internal class RedirectToUrlRule : IRule - { - /********* - ** Fields - *********/ - /// Get the new URL to which to redirect (or null to skip). - private readonly Func NewUrl; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// A predicate which indicates when the rule should be applied. - /// The new URL to which to redirect. - public RedirectToUrlRule(Func shouldRewrite, string url) - { - this.NewUrl = req => shouldRewrite(req) ? url : null; - } - - /// Construct an instance. - /// A case-insensitive regex to match against the path. - /// The external URL. - public RedirectToUrlRule(string pathRegex, string url) - { - Regex regex = new Regex(pathRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled); - this.NewUrl = req => req.Path.HasValue ? regex.Replace(req.Path.Value, url) : null; - } - - /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). - /// The rewrite context. - public void ApplyRule(RewriteContext context) - { - HttpRequest request = context.HttpContext.Request; - - // check rewrite - string newUrl = this.NewUrl(request); - if (newUrl == null || newUrl == request.Path.Value) - return; - - // redirect request - HttpResponse response = context.HttpContext.Response; - response.StatusCode = (int)HttpStatusCode.Redirect; - response.Headers["Location"] = newUrl; - context.Result = RuleResult.EndResponse; - } - } -} -- cgit From 9aba50451b617e1af8215358afda22c8105477f2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 May 2020 23:40:22 -0400 Subject: keep verb when redirecting api.smapi.io --- src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs index 16397c1e..d9d44641 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Rewrite; @@ -22,6 +23,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules /// Regex patterns matching the current URL mapped to the resulting redirect URL. public RedirectPathsToUrlsRule(IDictionary map) { + this.StatusCode = HttpStatusCode.RedirectKeepVerb; this.Map = map.ToDictionary( p => new Regex(p.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled), p => p.Value -- 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/Framework/Caching/Cached.cs | 37 ++++ src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs | 107 ---------- .../Framework/Caching/Mods/IModCacheRepository.cs | 5 +- .../Caching/Mods/ModCacheMemoryRepository.cs | 30 +-- .../Caching/Mods/ModCacheMongoRepository.cs | 105 ---------- .../Caching/UtcDateTimeOffsetSerializer.cs | 40 ---- .../Framework/Caching/Wiki/CachedWikiMetadata.cs | 43 ---- .../Framework/Caching/Wiki/CachedWikiMod.cs | 230 --------------------- .../Framework/Caching/Wiki/IWikiCacheRepository.cs | 9 +- .../Caching/Wiki/WikiCacheMemoryRepository.cs | 25 ++- .../Caching/Wiki/WikiCacheMongoRepository.cs | 73 ------- .../Framework/Caching/Wiki/WikiMetadata.cs | 31 +++ .../Framework/ConfigModels/StorageConfig.cs | 18 -- .../Framework/ConfigModels/StorageMode.cs | 15 -- 14 files changed, 96 insertions(+), 672 deletions(-) create mode 100644 src/SMAPI.Web/Framework/Caching/Cached.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Mods/ModCacheMongoRepository.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs delete mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs delete mode 100644 src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs delete mode 100644 src/SMAPI.Web/Framework/ConfigModels/StorageMode.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Cached.cs b/src/SMAPI.Web/Framework/Caching/Cached.cs new file mode 100644 index 00000000..52041a16 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Cached.cs @@ -0,0 +1,37 @@ +using System; + +namespace StardewModdingAPI.Web.Framework.Caching +{ + /// A cache entry. + /// The cached value type. + internal class Cached + { + /********* + ** Accessors + *********/ + /// The cached data. + public T Data { get; set; } + + /// When the data was last updated. + public DateTimeOffset LastUpdated { get; set; } + + /// When the data was last requested through the mod API. + public DateTimeOffset LastRequested { get; set; } + + + /********* + ** Public methods + *********/ + /// Construct an empty instance. + public Cached() { } + + /// Construct an instance. + /// The cached data. + public Cached(T data) + { + this.Data = data; + this.LastUpdated = DateTimeOffset.UtcNow; + this.LastRequested = DateTimeOffset.UtcNow; + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs b/src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs deleted file mode 100644 index 96eca847..00000000 --- a/src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using StardewModdingAPI.Toolkit.Framework.UpdateData; -using StardewModdingAPI.Web.Framework.ModRepositories; - -namespace StardewModdingAPI.Web.Framework.Caching.Mods -{ - /// The model for cached mod data. - internal class CachedMod - { - /********* - ** Accessors - *********/ - /**** - ** Tracking - ****/ - /// The internal MongoDB ID. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")] - [BsonIgnoreIfDefault] - public ObjectId _id { get; set; } - - /// When the data was last updated. - public DateTimeOffset LastUpdated { get; set; } - - /// When the data was last requested through the web API. - public DateTimeOffset LastRequested { get; set; } - - /**** - ** Metadata - ****/ - /// The mod site on which the mod is found. - public ModRepositoryKey Site { get; set; } - - /// The mod's unique ID within the . - public string ID { get; set; } - - /// The mod availability status on the remote site. - public RemoteModStatus FetchStatus { get; set; } - - /// The error message providing more info for the , if applicable. - public string FetchError { get; set; } - - - /**** - ** Mod info - ****/ - /// The mod's display name. - public string Name { get; set; } - - /// The mod's latest version. - public string MainVersion { get; set; } - - /// The mod's latest optional or prerelease version, if newer than . - public string PreviewVersion { get; set; } - - /// The URL for the mod page. - public string Url { get; set; } - - /// The license URL, if available. - public string LicenseUrl { get; set; } - - /// The license name, if available. - public string LicenseName { get; set; } - - - /********* - ** Accessors - *********/ - /// Construct an instance. - public CachedMod() { } - - /// Construct an instance. - /// The mod site on which the mod is found. - /// The mod's unique ID within the . - /// The mod data. - public CachedMod(ModRepositoryKey site, string id, ModInfoModel mod) - { - // tracking - this.LastUpdated = DateTimeOffset.UtcNow; - this.LastRequested = DateTimeOffset.UtcNow; - - // metadata - this.Site = site; - this.ID = id; - this.FetchStatus = mod.Status; - this.FetchError = mod.Error; - - // mod info - this.Name = mod.Name; - this.MainVersion = mod.Version; - this.PreviewVersion = mod.PreviewVersion; - this.Url = mod.Url; - this.LicenseUrl = mod.LicenseUrl; - this.LicenseName = mod.LicenseName; - } - - /// Get the API model for the cached data. - public ModInfoModel GetModel() - { - return new ModInfoModel(name: this.Name, version: this.MainVersion, url: this.Url, previewVersion: this.PreviewVersion) - .SetLicense(this.LicenseUrl, this.LicenseName) - .SetError(this.FetchStatus, this.FetchError); - } - } -} diff --git a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs index 08749f3b..004202f9 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs @@ -15,14 +15,13 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods /// The mod's unique ID within the . /// The fetched mod. /// Whether to update the mod's 'last requested' date. - bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true); + bool TryGetMod(ModRepositoryKey site, string id, out Cached mod, bool markRequested = true); /// Save data fetched for a mod. /// The mod site on which the mod is found. /// The mod's unique ID within the . /// The mod data. - /// The stored mod record. - void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod); + void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod); /// Delete data for mods which haven't been requested within a given time limit. /// The minimum age for which to remove mods. diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs index 9c5a217e..62461116 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods ** Fields *********/ /// The cached mod data indexed by {site key}:{ID}. - private readonly IDictionary Mods = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary> Mods = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); /********* @@ -24,19 +24,20 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods /// The mod's unique ID within the . /// The fetched mod. /// Whether to update the mod's 'last requested' date. - public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true) + public bool TryGetMod(ModRepositoryKey site, string id, out Cached mod, bool markRequested = true) { // get mod - if (!this.Mods.TryGetValue(this.GetKey(site, id), out mod)) + if (!this.Mods.TryGetValue(this.GetKey(site, id), out var cachedMod)) + { + mod = null; return false; + } // bump 'last requested' if (markRequested) - { - mod.LastRequested = DateTimeOffset.UtcNow; - mod = this.SaveMod(mod); - } +