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.
This commit is contained in:
parent
9aba50451b
commit
d7add89441
|
@ -23,7 +23,7 @@
|
|||
* Fixed `.pdb` files ignored for error stack traces for mods rewritten by SMAPI.
|
||||
|
||||
* For SMAPI developers:
|
||||
* When deploying web services to a single-instance app, the MongoDB server can now be replaced with in-memory storage.
|
||||
* Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed.
|
||||
* Merged the separate legacy redirects app on AWS into the main app on Azure.
|
||||
|
||||
## 3.5
|
||||
|
|
|
@ -352,7 +352,6 @@ your machine, with no external dependencies aside from the actual mod sites.
|
|||
--------------------------- | -----------
|
||||
`AzureBlobConnectionString` | The connection string for the Azure Blob storage account. Defaults to using the system's temporary file folder if not specified.
|
||||
`GitHubUsername`<br />`GitHubPassword` | The GitHub credentials with which to query GitHub release info. Defaults to anonymous requests if not specified.
|
||||
`Storage` | How to storage cached wiki/mod data. `InMemory` is recommended in most cases, or `MongoInMemory` to test the MongoDB storage code. See [production environment](#production-environment) for more info on `Mongo`.
|
||||
|
||||
2. Launch `SMAPI.Web` from Visual Studio to run a local version of the site.
|
||||
|
||||
|
@ -385,23 +384,4 @@ Initial setup:
|
|||
`Site:BetaBlurb` | If `Site:BetaEnabled` is true and there's a beta version of SMAPI in its GitHub releases, this is shown on the beta download button as explanatory subtext.
|
||||
`Site:SupporterList` | A list of Patreon supports to credit on the download page.
|
||||
|
||||
To enable distributed servers:
|
||||
|
||||
1. Launch an empty MongoDB server (e.g. using [MongoDB Atlas](https://www.mongodb.com/cloud/atlas))
|
||||
for mod data.
|
||||
2. Add these application settings in the App Services environment:
|
||||
|
||||
property name | description
|
||||
------------------------------- | -----------------
|
||||
`Storage:Mode` | Set to `Mongo`.
|
||||
`Storage:ConnectionString` | Set to the connection string for the MongoDB instance.
|
||||
|
||||
Optional settings:
|
||||
|
||||
property name | description
|
||||
------------------------------- | -----------------
|
||||
`Storage:Database` | Set to the MongoDB database name (defaults to `smapi`).
|
||||
|
||||
To deploy updates:
|
||||
1. [Deploy the web project from Visual Studio](https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-to-azure).
|
||||
2. If the MongoDB schema changed, delete the MongoDB database. (It'll be recreated automatically.)
|
||||
To deploy updates, just [redeploy the web project from Visual Studio](https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-to-azure).
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace StardewModdingAPI.Web
|
|||
public static async Task UpdateWikiAsync()
|
||||
{
|
||||
WikiModList wikiCompatList = await new ModToolkit().GetWikiCompatibilityListAsync();
|
||||
BackgroundService.WikiCache.SaveWikiData(wikiCompatList.StableVersion, wikiCompatList.BetaVersion, wikiCompatList.Mods, out _, out _);
|
||||
BackgroundService.WikiCache.SaveWikiData(wikiCompatList.StableVersion, wikiCompatList.BetaVersion, wikiCompatList.Mods);
|
||||
}
|
||||
|
||||
/// <summary>Remove mods which haven't been requested in over 48 hours.</summary>
|
||||
|
|
|
@ -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<string, ModEntryModel> mods = new Dictionary<string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase);
|
||||
foreach (ModSearchEntryModel mod in model.Mods)
|
||||
{
|
||||
|
@ -283,27 +284,30 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
|
||||
private async Task<ModInfoModel> 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<ModInfoModel> 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();
|
||||
}
|
||||
|
||||
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary>
|
||||
|
|
|
@ -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<WikiMetadata> 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)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching
|
||||
{
|
||||
/// <summary>A cache entry.</summary>
|
||||
/// <typeparam name="T">The cached value type.</typeparam>
|
||||
internal class Cached<T>
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The cached data.</summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
/// <summary>When the data was last updated.</summary>
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
|
||||
/// <summary>When the data was last requested through the mod API.</summary>
|
||||
public DateTimeOffset LastRequested { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public Cached() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="data">The cached data.</param>
|
||||
public Cached(T data)
|
||||
{
|
||||
this.Data = data;
|
||||
this.LastUpdated = DateTimeOffset.UtcNow;
|
||||
this.LastRequested = DateTimeOffset.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>The model for cached mod data.</summary>
|
||||
internal class CachedMod
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/****
|
||||
** Tracking
|
||||
****/
|
||||
/// <summary>The internal MongoDB ID.</summary>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")]
|
||||
[BsonIgnoreIfDefault]
|
||||
public ObjectId _id { get; set; }
|
||||
|
||||
/// <summary>When the data was last updated.</summary>
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
|
||||
/// <summary>When the data was last requested through the web API.</summary>
|
||||
public DateTimeOffset LastRequested { get; set; }
|
||||
|
||||
/****
|
||||
** Metadata
|
||||
****/
|
||||
/// <summary>The mod site on which the mod is found.</summary>
|
||||
public ModRepositoryKey Site { get; set; }
|
||||
|
||||
/// <summary>The mod's unique ID within the <see cref="Site"/>.</summary>
|
||||
public string ID { get; set; }
|
||||
|
||||
/// <summary>The mod availability status on the remote site.</summary>
|
||||
public RemoteModStatus FetchStatus { get; set; }
|
||||
|
||||
/// <summary>The error message providing more info for the <see cref="FetchStatus"/>, if applicable.</summary>
|
||||
public string FetchError { get; set; }
|
||||
|
||||
|
||||
/****
|
||||
** Mod info
|
||||
****/
|
||||
/// <summary>The mod's display name.</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>The mod's latest version.</summary>
|
||||
public string MainVersion { get; set; }
|
||||
|
||||
/// <summary>The mod's latest optional or prerelease version, if newer than <see cref="MainVersion"/>.</summary>
|
||||
public string PreviewVersion { get; set; }
|
||||
|
||||
/// <summary>The URL for the mod page.</summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>The license URL, if available.</summary>
|
||||
public string LicenseUrl { get; set; }
|
||||
|
||||
/// <summary>The license name, if available.</summary>
|
||||
public string LicenseName { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public CachedMod() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="site">The mod site on which the mod is found.</param>
|
||||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Get the API model for the cached data.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,14 +15,13 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The fetched mod.</param>
|
||||
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
|
||||
bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true);
|
||||
bool TryGetMod(ModRepositoryKey site, string id, out Cached<ModInfoModel> mod, bool markRequested = true);
|
||||
|
||||
/// <summary>Save data fetched for a mod.</summary>
|
||||
/// <param name="site">The mod site on which the mod is found.</param>
|
||||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
/// <param name="cachedMod">The stored mod record.</param>
|
||||
void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod);
|
||||
void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod);
|
||||
|
||||
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
|
||||
/// <param name="age">The minimum age for which to remove mods.</param>
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The cached mod data indexed by <c>{site key}:{ID}</c>.</summary>
|
||||
private readonly IDictionary<string, CachedMod> Mods = new Dictionary<string, CachedMod>(StringComparer.InvariantCultureIgnoreCase);
|
||||
private readonly IDictionary<string, Cached<ModInfoModel>> Mods = new Dictionary<string, Cached<ModInfoModel>>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -24,19 +24,20 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The fetched mod.</param>
|
||||
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
|
||||
public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true)
|
||||
public bool TryGetMod(ModRepositoryKey site, string id, out Cached<ModInfoModel> 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);
|
||||
}
|
||||
cachedMod.LastRequested = DateTimeOffset.UtcNow;
|
||||
|
||||
mod = cachedMod;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -44,11 +45,10 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
/// <param name="site">The mod site on which the mod is found.</param>
|
||||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
/// <param name="cachedMod">The stored mod record.</param>
|
||||
public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod)
|
||||
public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod)
|
||||
{
|
||||
string key = this.GetKey(site, id);
|
||||
cachedMod = this.SaveMod(new CachedMod(site, id, mod));
|
||||
this.Mods[key] = new Cached<ModInfoModel>(mod);
|
||||
}
|
||||
|
||||
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
|
||||
|
@ -66,14 +66,6 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
this.Mods.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>Save data fetched for a mod.</summary>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
public CachedMod SaveMod(CachedMod mod)
|
||||
{
|
||||
string key = this.GetKey(mod.Site, mod.ID);
|
||||
return this.Mods[key] = mod;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
|
@ -81,7 +73,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
/// <summary>Get a cache key.</summary>
|
||||
/// <param name="site">The mod site.</param>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public string GetKey(ModRepositoryKey site, string id)
|
||||
private string GetKey(ModRepositoryKey site, string id)
|
||||
{
|
||||
return $"{site}:{id.Trim()}".ToLower();
|
||||
}
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
using System;
|
||||
using MongoDB.Driver;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
using StardewModdingAPI.Web.Framework.ModRepositories;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
||||
{
|
||||
/// <summary>Manages cached mod data in MongoDB.</summary>
|
||||
internal class ModCacheMongoRepository : BaseCacheRepository, IModCacheRepository
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The collection for cached mod data.</summary>
|
||||
private readonly IMongoCollection<CachedMod> Mods;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="database">The authenticated MongoDB database.</param>
|
||||
public ModCacheMongoRepository(IMongoDatabase database)
|
||||
{
|
||||
// get collections
|
||||
this.Mods = database.GetCollection<CachedMod>("mods");
|
||||
|
||||
// add indexes if needed
|
||||
this.Mods.Indexes.CreateOne(new CreateIndexModel<CachedMod>(Builders<CachedMod>.IndexKeys.Ascending(p => p.ID).Ascending(p => p.Site)));
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get the cached mod data.</summary>
|
||||
/// <param name="site">The mod site to search.</param>
|
||||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The fetched mod.</param>
|
||||
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Save data fetched for a mod.</summary>
|
||||
/// <param name="site">The mod site on which the mod is found.</param>
|
||||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
/// <param name="cachedMod">The stored mod record.</param>
|
||||
public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod)
|
||||
{
|
||||
id = this.NormalizeId(id);
|
||||
|
||||
cachedMod = this.SaveMod(new CachedMod(site, id, mod));
|
||||
}
|
||||
|
||||
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
|
||||
/// <param name="age">The minimum age for which to remove mods.</param>
|
||||
public void RemoveStaleMods(TimeSpan age)
|
||||
{
|
||||
DateTimeOffset minDate = DateTimeOffset.UtcNow.Subtract(age);
|
||||
this.Mods.DeleteMany(p => p.LastRequested < minDate);
|
||||
}
|
||||
|
||||
/// <summary>Save data fetched for a mod.</summary>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
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
|
||||
*********/
|
||||
/// <summary>Normalize a mod ID for case-insensitive search.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public string NormalizeId(string id)
|
||||
{
|
||||
return id.Trim().ToLower();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using System;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Bson.Serialization.Serializers;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching
|
||||
{
|
||||
/// <summary>Serializes <see cref="DateTimeOffset"/> to a UTC date field instead of the default array.</summary>
|
||||
public class UtcDateTimeOffsetSerializer : StructSerializerBase<DateTimeOffset>
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The underlying date serializer.</summary>
|
||||
private static readonly DateTimeSerializer DateTimeSerializer = new DateTimeSerializer(DateTimeKind.Utc, BsonType.DateTime);
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Deserializes a value.</summary>
|
||||
/// <param name="context">The deserialization context.</param>
|
||||
/// <param name="args">The deserialization args.</param>
|
||||
/// <returns>A deserialized value.</returns>
|
||||
public override DateTimeOffset Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
|
||||
{
|
||||
DateTime date = UtcDateTimeOffsetSerializer.DateTimeSerializer.Deserialize(context, args);
|
||||
return new DateTimeOffset(date, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
/// <summary>Serializes a value.</summary>
|
||||
/// <param name="context">The serialization context.</param>
|
||||
/// <param name="args">The serialization args.</param>
|
||||
/// <param name="value">The object.</param>
|
||||
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTimeOffset value)
|
||||
{
|
||||
UtcDateTimeOffsetSerializer.DateTimeSerializer.Serialize(context, args, value.UtcDateTime);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using MongoDB.Bson.Serialization.Options;
|
||||
using StardewModdingAPI.Toolkit;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
||||
{
|
||||
/// <summary>The model for cached wiki mods.</summary>
|
||||
internal class CachedWikiMod
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/****
|
||||
** Tracking
|
||||
****/
|
||||
/// <summary>The internal MongoDB ID.</summary>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")]
|
||||
public ObjectId _id { get; set; }
|
||||
|
||||
/// <summary>When the data was last updated.</summary>
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
|
||||
/****
|
||||
** Mod info
|
||||
****/
|
||||
/// <summary>The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order.</summary>
|
||||
public string[] ID { get; set; }
|
||||
|
||||
/// <summary>The mod's display name. If the mod has multiple names, the first one is the most canonical name.</summary>
|
||||
public string[] Name { get; set; }
|
||||
|
||||
/// <summary>The mod's author name. If the author has multiple names, the first one is the most canonical name.</summary>
|
||||
public string[] Author { get; set; }
|
||||
|
||||
/// <summary>The mod ID on Nexus.</summary>
|
||||
public int? NexusID { get; set; }
|
||||
|
||||
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
|
||||
public int? ChucklefishID { get; set; }
|
||||
|
||||
/// <summary>The mod ID in the CurseForge mod repo.</summary>
|
||||
public int? CurseForgeID { get; set; }
|
||||
|
||||
/// <summary>The mod key in the CurseForge mod repo (used in mod page URLs).</summary>
|
||||
public string CurseForgeKey { get; set; }
|
||||
|
||||
/// <summary>The mod ID in the ModDrop mod repo.</summary>
|
||||
public int? ModDropID { get; set; }
|
||||
|
||||
/// <summary>The GitHub repository in the form 'owner/repo'.</summary>
|
||||
public string GitHubRepo { get; set; }
|
||||
|
||||
/// <summary>The URL to a non-GitHub source repo.</summary>
|
||||
public string CustomSourceUrl { get; set; }
|
||||
|
||||
/// <summary>The custom mod page URL (if applicable).</summary>
|
||||
public string CustomUrl { get; set; }
|
||||
|
||||
/// <summary>The name of the mod which loads this content pack, if applicable.</summary>
|
||||
public string ContentPackFor { get; set; }
|
||||
|
||||
/// <summary>The human-readable warnings for players about this mod.</summary>
|
||||
public string[] Warnings { get; set; }
|
||||
|
||||
/// <summary>The URL of the pull request which submits changes for an unofficial update to the author, if any.</summary>
|
||||
public string PullRequestUrl { get; set; }
|
||||
|
||||
/// <summary>Special notes intended for developers who maintain unofficial updates or submit pull requests. </summary>
|
||||
public string DevNote { get; set; }
|
||||
|
||||
/// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary>
|
||||
public string Anchor { get; set; }
|
||||
|
||||
/****
|
||||
** Stable compatibility
|
||||
****/
|
||||
/// <summary>The compatibility status.</summary>
|
||||
public WikiCompatibilityStatus MainStatus { get; set; }
|
||||
|
||||
/// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary>
|
||||
public string MainSummary { get; set; }
|
||||
|
||||
/// <summary>The game or SMAPI version which broke this mod (if applicable).</summary>
|
||||
public string MainBrokeIn { get; set; }
|
||||
|
||||
/// <summary>The version of the latest unofficial update, if applicable.</summary>
|
||||
public string MainUnofficialVersion { get; set; }
|
||||
|
||||
/// <summary>The URL to the latest unofficial update, if applicable.</summary>
|
||||
public string MainUnofficialUrl { get; set; }
|
||||
|
||||
/****
|
||||
** Beta compatibility
|
||||
****/
|
||||
/// <summary>The compatibility status.</summary>
|
||||
public WikiCompatibilityStatus? BetaStatus { get; set; }
|
||||
|
||||
/// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary>
|
||||
public string BetaSummary { get; set; }
|
||||
|
||||
/// <summary>The game or SMAPI version which broke this mod (if applicable).</summary>
|
||||
public string BetaBrokeIn { get; set; }
|
||||
|
||||
/// <summary>The version of the latest unofficial update, if applicable.</summary>
|
||||
public string BetaUnofficialVersion { get; set; }
|
||||
|
||||
/// <summary>The URL to the latest unofficial update, if applicable.</summary>
|
||||
public string BetaUnofficialUrl { get; set; }
|
||||
|
||||
/****
|
||||
** Version maps
|
||||
****/
|
||||
/// <summary>Maps local versions to a semantic version for update checks.</summary>
|
||||
[BsonDictionaryOptions(Representation = DictionaryRepresentation.ArrayOfArrays)]
|
||||
public IDictionary<string, string> MapLocalVersions { get; set; }
|
||||
|
||||
/// <summary>Maps remote versions to a semantic version for update checks.</summary>
|
||||
[BsonDictionaryOptions(Representation = DictionaryRepresentation.ArrayOfArrays)]
|
||||
public IDictionary<string, string> MapRemoteVersions { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public CachedWikiMod() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="mod">The mod data.</param>
|
||||
public CachedWikiMod(WikiModEntry mod)
|
||||
{
|
||||
// tracking
|
||||
this.LastUpdated = DateTimeOffset.UtcNow;
|
||||
|
||||
// mod info
|
||||
this.ID = mod.ID;
|
||||
this.Name = mod.Name;
|
||||
this.Author = mod.Author;
|
||||
this.NexusID = mod.NexusID;
|
||||
this.ChucklefishID = mod.ChucklefishID;
|
||||
this.CurseForgeID = mod.CurseForgeID;
|
||||
this.CurseForgeKey = mod.CurseForgeKey;
|
||||
this.ModDropID = mod.ModDropID;
|
||||
this.GitHubRepo = mod.GitHubRepo;
|
||||
this.CustomSourceUrl = mod.CustomSourceUrl;
|
||||
this.CustomUrl = mod.CustomUrl;
|
||||
this.ContentPackFor = mod.ContentPackFor;
|
||||
this.PullRequestUrl = mod.PullRequestUrl;
|
||||
this.Warnings = mod.Warnings;
|
||||
this.DevNote = mod.DevNote;
|
||||
this.Anchor = mod.Anchor;
|
||||
|
||||
// stable compatibility
|
||||
this.MainStatus = mod.Compatibility.Status;
|
||||
this.MainSummary = mod.Compatibility.Summary;
|
||||
this.MainBrokeIn = mod.Compatibility.BrokeIn;
|
||||
this.MainUnofficialVersion = mod.Compatibility.UnofficialVersion?.ToString();
|
||||
this.MainUnofficialUrl = mod.Compatibility.UnofficialUrl;
|
||||
|
||||
// beta compatibility
|
||||
this.BetaStatus = mod.BetaCompatibility?.Status;
|
||||
this.BetaSummary = mod.BetaCompatibility?.Summary;
|
||||
this.BetaBrokeIn = mod.BetaCompatibility?.BrokeIn;
|
||||
this.BetaUnofficialVersion = mod.BetaCompatibility?.UnofficialVersion?.ToString();
|
||||
this.BetaUnofficialUrl = mod.BetaCompatibility?.UnofficialUrl;
|
||||
|
||||
// version maps
|
||||
this.MapLocalVersions = mod.MapLocalVersions;
|
||||
this.MapRemoteVersions = mod.MapRemoteVersions;
|
||||
}
|
||||
|
||||
/// <summary>Reconstruct the original model.</summary>
|
||||
public WikiModEntry GetModel()
|
||||
{
|
||||
var mod = new WikiModEntry
|
||||
{
|
||||
ID = this.ID,
|
||||
Name = this.Name,
|
||||
Author = this.Author,
|
||||
NexusID = this.NexusID,
|
||||
ChucklefishID = this.ChucklefishID,
|
||||
CurseForgeID = this.CurseForgeID,
|
||||
CurseForgeKey = this.CurseForgeKey,
|
||||
ModDropID = this.ModDropID,
|
||||
GitHubRepo = this.GitHubRepo,
|
||||
CustomSourceUrl = this.CustomSourceUrl,
|
||||
CustomUrl = this.CustomUrl,
|
||||
ContentPackFor = this.ContentPackFor,
|
||||
Warnings = this.Warnings,
|
||||
PullRequestUrl = this.PullRequestUrl,
|
||||
DevNote = this.DevNote,
|
||||
Anchor = this.Anchor,
|
||||
|
||||
// stable compatibility
|
||||
Compatibility = new WikiCompatibilityInfo
|
||||
{
|
||||
Status = this.MainStatus,
|
||||
Summary = this.MainSummary,
|
||||
BrokeIn = this.MainBrokeIn,
|
||||
UnofficialVersion = this.MainUnofficialVersion != null ? new SemanticVersion(this.MainUnofficialVersion) : null,
|
||||
UnofficialUrl = this.MainUnofficialUrl
|
||||
},
|
||||
|
||||
// version maps
|
||||
MapLocalVersions = this.MapLocalVersions,
|
||||
MapRemoteVersions = this.MapRemoteVersions
|
||||
};
|
||||
|
||||
// beta compatibility
|
||||
if (this.BetaStatus != null)
|
||||
{
|
||||
mod.BetaCompatibility = new WikiCompatibilityInfo
|
||||
{
|
||||
Status = this.BetaStatus.Value,
|
||||
Summary = this.BetaSummary,
|
||||
BrokeIn = this.BetaBrokeIn,
|
||||
UnofficialVersion = this.BetaUnofficialVersion != null ? new SemanticVersion(this.BetaUnofficialVersion) : null,
|
||||
UnofficialUrl = this.BetaUnofficialUrl
|
||||
};
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
||||
|
@ -13,18 +12,16 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
*********/
|
||||
/// <summary>Get the cached wiki metadata.</summary>
|
||||
/// <param name="metadata">The fetched metadata.</param>
|
||||
bool TryGetWikiMetadata(out CachedWikiMetadata metadata);
|
||||
bool TryGetWikiMetadata(out Cached<WikiMetadata> metadata);
|
||||
|
||||
/// <summary>Get the cached wiki mods.</summary>
|
||||
/// <param name="filter">A filter to apply, if any.</param>
|
||||
IEnumerable<CachedWikiMod> GetWikiMods(Expression<Func<CachedWikiMod, bool>> filter = null);
|
||||
IEnumerable<Cached<WikiModEntry>> GetWikiMods(Func<WikiModEntry, bool> filter = null);
|
||||
|
||||
/// <summary>Save data fetched from the wiki compatibility list.</summary>
|
||||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
/// <param name="mods">The mod data.</param>
|
||||
/// <param name="cachedMetadata">The stored metadata record.</param>
|
||||
/// <param name="cachedMods">The stored mod records.</param>
|
||||
void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods);
|
||||
void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> mods);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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
|
||||
|
@ -13,10 +12,10 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The saved wiki metadata.</summary>
|
||||
private CachedWikiMetadata Metadata;
|
||||
private Cached<WikiMetadata> Metadata;
|
||||
|
||||
/// <summary>The cached wiki data.</summary>
|
||||
private CachedWikiMod[] Mods = new CachedWikiMod[0];
|
||||
private Cached<WikiModEntry>[] Mods = new Cached<WikiModEntry>[0];
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -24,7 +23,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
*********/
|
||||
/// <summary>Get the cached wiki metadata.</summary>
|
||||
/// <param name="metadata">The fetched metadata.</param>
|
||||
public bool TryGetWikiMetadata(out CachedWikiMetadata metadata)
|
||||
public bool TryGetWikiMetadata(out Cached<WikiMetadata> metadata)
|
||||
{
|
||||
metadata = this.Metadata;
|
||||
return metadata != null;
|
||||
|
@ -32,23 +31,23 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
|
||||
/// <summary>Get the cached wiki mods.</summary>
|
||||
/// <param name="filter">A filter to apply, if any.</param>
|
||||
public IEnumerable<CachedWikiMod> GetWikiMods(Expression<Func<CachedWikiMod, bool>> filter = null)
|
||||
public IEnumerable<Cached<WikiModEntry>> GetWikiMods(Func<WikiModEntry, bool> filter = null)
|
||||
{
|
||||
return filter != null
|
||||
? this.Mods.Where(filter.Compile())
|
||||
: this.Mods.ToArray();
|
||||
foreach (var mod in this.Mods)
|
||||
{
|
||||
if (filter == null || filter(mod.Data))
|
||||
yield return mod;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Save data fetched from the wiki compatibility list.</summary>
|
||||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
/// <param name="mods">The mod data.</param>
|
||||
/// <param name="cachedMetadata">The stored metadata record.</param>
|
||||
/// <param name="cachedMods">The stored mod records.</param>
|
||||
public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods)
|
||||
public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> mods)
|
||||
{
|
||||
this.Metadata = cachedMetadata = new CachedWikiMetadata(stableVersion, betaVersion);
|
||||
this.Mods = cachedMods = mods.Select(mod => new CachedWikiMod(mod)).ToArray();
|
||||
this.Metadata = new Cached<WikiMetadata>(new WikiMetadata(stableVersion, betaVersion));
|
||||
this.Mods = mods.Select(mod => new Cached<WikiModEntry>(mod)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>Manages cached wiki data in MongoDB.</summary>
|
||||
internal class WikiCacheMongoRepository : BaseCacheRepository, IWikiCacheRepository
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The collection for wiki metadata.</summary>
|
||||
private readonly IMongoCollection<CachedWikiMetadata> Metadata;
|
||||
|
||||
/// <summary>The collection for wiki mod data.</summary>
|
||||
private readonly IMongoCollection<CachedWikiMod> Mods;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="database">The authenticated MongoDB database.</param>
|
||||
public WikiCacheMongoRepository(IMongoDatabase database)
|
||||
{
|
||||
// get collections
|
||||
this.Metadata = database.GetCollection<CachedWikiMetadata>("wiki-metadata");
|
||||
this.Mods = database.GetCollection<CachedWikiMod>("wiki-mods");
|
||||
|
||||
// add indexes if needed
|
||||
this.Mods.Indexes.CreateOne(new CreateIndexModel<CachedWikiMod>(Builders<CachedWikiMod>.IndexKeys.Ascending(p => p.ID)));
|
||||
}
|
||||
|
||||
/// <summary>Get the cached wiki metadata.</summary>
|
||||
/// <param name="metadata">The fetched metadata.</param>
|
||||
public bool TryGetWikiMetadata(out CachedWikiMetadata metadata)
|
||||
{
|
||||
metadata = this.Metadata.Find("{}").FirstOrDefault();
|
||||
return metadata != null;
|
||||
}
|
||||
|
||||
/// <summary>Get the cached wiki mods.</summary>
|
||||
/// <param name="filter">A filter to apply, if any.</param>
|
||||
public IEnumerable<CachedWikiMod> GetWikiMods(Expression<Func<CachedWikiMod, bool>> filter = null)
|
||||
{
|
||||
return filter != null
|
||||
? this.Mods.Find(filter).ToList()
|
||||
: this.Mods.Find("{}").ToList();
|
||||
}
|
||||
|
||||
/// <summary>Save data fetched from the wiki compatibility list.</summary>
|
||||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
/// <param name="mods">The mod data.</param>
|
||||
/// <param name="cachedMetadata">The stored metadata record.</param>
|
||||
/// <param name="cachedMods">The stored mod records.</param>
|
||||
public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
||||
{
|
||||
/// <summary>The model for cached wiki metadata.</summary>
|
||||
internal class CachedWikiMetadata
|
||||
internal class WikiMetadata
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The internal MongoDB ID.</summary>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")]
|
||||
public ObjectId _id { get; set; }
|
||||
|
||||
/// <summary>When the data was last updated.</summary>
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
|
||||
/// <summary>The current stable Stardew Valley version.</summary>
|
||||
public string StableVersion { get; set; }
|
||||
|
||||
|
@ -28,16 +17,15 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public CachedWikiMetadata() { }
|
||||
public WikiMetadata() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
public CachedWikiMetadata(string stableVersion, string betaVersion)
|
||||
public WikiMetadata(string stableVersion, string betaVersion)
|
||||
{
|
||||
this.StableVersion = stableVersion;
|
||||
this.BetaVersion = betaVersion;
|
||||
this.LastUpdated = DateTimeOffset.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
/// <summary>The config settings for cache storage.</summary>
|
||||
internal class StorageConfig
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The storage mechanism to use.</summary>
|
||||
public StorageMode Mode { get; set; }
|
||||
|
||||
/// <summary>The connection string for the storage mechanism, if applicable.</summary>
|
||||
public string ConnectionString { get; set; }
|
||||
|
||||
/// <summary>The database name for the storage mechanism, if applicable.</summary>
|
||||
public string Database { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
/// <summary>Indicates a storage mechanism to use.</summary>
|
||||
internal enum StorageMode
|
||||
{
|
||||
/// <summary>Store data in a hosted MongoDB instance.</summary>
|
||||
Mongo,
|
||||
|
||||
/// <summary>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.</summary>
|
||||
MongoInMemory,
|
||||
|
||||
/// <summary>Store data in-memory. This is suitable for local testing or single-instance servers, but will cause issues when distributed across multiple servers.</summary>
|
||||
InMemory
|
||||
}
|
||||
}
|
|
@ -15,14 +15,11 @@
|
|||
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.2" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.11" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
|
||||
<PackageReference Include="Hangfire.Mongo" Version="0.6.7" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.23" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.8.11" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
|
||||
<PackageReference Include="Markdig" Version="0.20.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.2" />
|
||||
<PackageReference Include="Mongo2Go" Version="2.2.12" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Pathoschild.FluentNexus" Version="1.0.1" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.0.0" />
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Hangfire;
|
||||
using Hangfire.MemoryStorage;
|
||||
using Hangfire.Mongo;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
|
@ -11,13 +9,9 @@ using Microsoft.AspNetCore.Routing;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
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;
|
||||
|
@ -68,13 +62,10 @@ namespace StardewModdingAPI.Web
|
|||
.Configure<BackgroundServicesConfig>(this.Configuration.GetSection("BackgroundServices"))
|
||||
.Configure<ModCompatibilityListConfig>(this.Configuration.GetSection("ModCompatibilityList"))
|
||||
.Configure<ModUpdateCheckConfig>(this.Configuration.GetSection("ModUpdateCheck"))
|
||||
.Configure<StorageConfig>(this.Configuration.GetSection("Storage"))
|
||||
.Configure<SiteConfig>(this.Configuration.GetSection("Site"))
|
||||
.Configure<RouteOptions>(options => options.ConstraintMap.Add("semanticVersion", typeof(VersionConstraint)))
|
||||
.AddLogging()
|
||||
.AddMemoryCache();
|
||||
StorageConfig storageConfig = this.Configuration.GetSection("Storage").Get<StorageConfig>();
|
||||
StorageMode storageMode = storageConfig.Mode;
|
||||
|
||||
// init MVC
|
||||
services
|
||||
|
@ -85,39 +76,8 @@ namespace StardewModdingAPI.Web
|
|||
.AddRazorPages();
|
||||
|
||||
// init storage
|
||||
switch (storageMode)
|
||||
{
|
||||
case StorageMode.InMemory:
|
||||
services.AddSingleton<IModCacheRepository>(new ModCacheMemoryRepository());
|
||||
services.AddSingleton<IWikiCacheRepository>(new WikiCacheMemoryRepository());
|
||||
break;
|
||||
|
||||
case StorageMode.Mongo:
|
||||
case StorageMode.MongoInMemory:
|
||||
{
|
||||
// local MongoDB instance
|
||||
services.AddSingleton<MongoDbRunner>(_ => storageMode == StorageMode.MongoInMemory
|
||||
? MongoDbRunner.Start()
|
||||
: throw new NotSupportedException($"The in-memory MongoDB runner isn't available in storage mode {storageMode}.")
|
||||
);
|
||||
|
||||
// MongoDB
|
||||
services.AddSingleton<IMongoDatabase>(serv =>
|
||||
{
|
||||
BsonSerializer.RegisterSerializer(new UtcDateTimeOffsetSerializer());
|
||||
return new MongoClient(this.GetMongoDbConnectionString(serv, storageConfig))
|
||||
.GetDatabase(storageConfig.Database);
|
||||
});
|
||||
|
||||
// repositories
|
||||
services.AddSingleton<IModCacheRepository>(serv => new ModCacheMongoRepository(serv.GetRequiredService<IMongoDatabase>()));
|
||||
services.AddSingleton<IWikiCacheRepository>(serv => new WikiCacheMongoRepository(serv.GetRequiredService<IMongoDatabase>()));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unhandled storage mode '{storageMode}'.");
|
||||
}
|
||||
services.AddSingleton<IModCacheRepository>(new ModCacheMemoryRepository());
|
||||
services.AddSingleton<IWikiCacheRepository>(new WikiCacheMemoryRepository());
|
||||
|
||||
// init Hangfire
|
||||
services
|
||||
|
@ -126,24 +86,8 @@ namespace StardewModdingAPI.Web
|
|||
config
|
||||
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
|
||||
.UseSimpleAssemblyNameTypeSerializer()
|
||||
.UseRecommendedSerializerSettings();
|
||||
|
||||
switch (storageMode)
|
||||
{
|
||||
case StorageMode.InMemory:
|
||||
config.UseMemoryStorage();
|
||||
break;
|
||||
|
||||
case StorageMode.MongoInMemory:
|
||||
case StorageMode.Mongo:
|
||||
string connectionString = this.GetMongoDbConnectionString(serv, storageConfig);
|
||||
config.UseMongoStorage(MongoClientSettings.FromConnectionString(connectionString), $"{storageConfig.Database}-hangfire", new MongoStorageOptions
|
||||
{
|
||||
MigrationOptions = new MongoMigrationOptions(MongoMigrationStrategy.Drop),
|
||||
CheckConnection = false // error on startup takes down entire process
|
||||
});
|
||||
break;
|
||||
}
|
||||
.UseRecommendedSerializerSettings()
|
||||
.UseMemoryStorage();
|
||||
});
|
||||
|
||||
// init background service
|
||||
|
@ -254,20 +198,6 @@ namespace StardewModdingAPI.Web
|
|||
settings.NullValueHandling = NullValueHandling.Ignore;
|
||||
}
|
||||
|
||||
/// <summary>Get the MongoDB connection string for the given storage configuration.</summary>
|
||||
/// <param name="services">The service provider.</param>
|
||||
/// <param name="storageConfig">The storage configuration</param>
|
||||
/// <exception cref="NotSupportedException">There's no MongoDB instance in the given storage mode.</exception>
|
||||
private string GetMongoDbConnectionString(IServiceProvider services, StorageConfig storageConfig)
|
||||
{
|
||||
return storageConfig.Mode switch
|
||||
{
|
||||
StorageMode.Mongo => storageConfig.ConnectionString,
|
||||
StorageMode.MongoInMemory => services.GetRequiredService<MongoDbRunner>().ConnectionString,
|
||||
_ => throw new NotSupportedException($"There's no MongoDB instance in storage mode {storageConfig.Mode}.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Get the redirect rules to apply.</summary>
|
||||
private RewriteOptions GetRedirectRules()
|
||||
{
|
||||
|
|
|
@ -17,12 +17,6 @@
|
|||
"NexusApiKey": null
|
||||
},
|
||||
|
||||
"Storage": {
|
||||
"Mode": "MongoInMemory",
|
||||
"ConnectionString": null,
|
||||
"Database": "smapi-edge"
|
||||
},
|
||||
|
||||
"BackgroundServices": {
|
||||
"Enabled": true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue