Skip to content

Commit d7add89

Browse files
committed
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.
1 parent 9aba504 commit d7add89

21 files changed

Lines changed: 95 additions & 765 deletions

docs/release-notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* Fixed `.pdb` files ignored for error stack traces for mods rewritten by SMAPI.
2424

2525
* For SMAPI developers:
26-
* When deploying web services to a single-instance app, the MongoDB server can now be replaced with in-memory storage.
26+
* 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.
2727
* Merged the separate legacy redirects app on AWS into the main app on Azure.
2828

2929
## 3.5

docs/technical/web.md

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,6 @@ your machine, with no external dependencies aside from the actual mod sites.
352352
--------------------------- | -----------
353353
`AzureBlobConnectionString` | The connection string for the Azure Blob storage account. Defaults to using the system's temporary file folder if not specified.
354354
`GitHubUsername`<br />`GitHubPassword` | The GitHub credentials with which to query GitHub release info. Defaults to anonymous requests if not specified.
355-
`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`.
356355

357356
2. Launch `SMAPI.Web` from Visual Studio to run a local version of the site.
358357

@@ -385,23 +384,4 @@ Initial setup:
385384
`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.
386385
`Site:SupporterList` | A list of Patreon supports to credit on the download page.
387386

388-
To enable distributed servers:
389-
390-
1. Launch an empty MongoDB server (e.g. using [MongoDB Atlas](https://www.mongodb.com/cloud/atlas))
391-
for mod data.
392-
2. Add these application settings in the App Services environment:
393-
394-
property name | description
395-
------------------------------- | -----------------
396-
`Storage:Mode` | Set to `Mongo`.
397-
`Storage:ConnectionString` | Set to the connection string for the MongoDB instance.
398-
399-
Optional settings:
400-
401-
property name | description
402-
------------------------------- | -----------------
403-
`Storage:Database` | Set to the MongoDB database name (defaults to `smapi`).
404-
405-
To deploy updates:
406-
1. [Deploy the web project from Visual Studio](https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-to-azure).
407-
2. If the MongoDB schema changed, delete the MongoDB database. (It'll be recreated automatically.)
387+
To deploy updates, just [redeploy the web project from Visual Studio](https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-to-azure).

src/SMAPI.Web/BackgroundService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public void Dispose()
8484
public static async Task UpdateWikiAsync()
8585
{
8686
WikiModList wikiCompatList = await new ModToolkit().GetWikiCompatibilityListAsync();
87-
BackgroundService.WikiCache.SaveWikiData(wikiCompatList.StableVersion, wikiCompatList.BetaVersion, wikiCompatList.Mods, out _, out _);
87+
BackgroundService.WikiCache.SaveWikiData(wikiCompatList.StableVersion, wikiCompatList.BetaVersion, wikiCompatList.Mods);
8888
}
8989

9090
/// <summary>Remove mods which haven't been requested in over 48 hours.</summary>

src/SMAPI.Web/Controllers/ModsApiController.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using StardewModdingAPI.Toolkit.Framework.ModData;
1313
using StardewModdingAPI.Toolkit.Framework.UpdateData;
1414
using StardewModdingAPI.Web.Framework;
15+
using StardewModdingAPI.Web.Framework.Caching;
1516
using StardewModdingAPI.Web.Framework.Caching.Mods;
1617
using StardewModdingAPI.Web.Framework.Caching.Wiki;
1718
using StardewModdingAPI.Web.Framework.Clients.Chucklefish;
@@ -90,7 +91,7 @@ public async Task<IEnumerable<ModEntryModel>> PostAsync([FromBody] ModSearchMode
9091
return new ModEntryModel[0];
9192

9293
// fetch wiki data
93-
WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.GetModel()).ToArray();
94+
WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.Data).ToArray();
9495
IDictionary<string, ModEntryModel> mods = new Dictionary<string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase);
9596
foreach (ModSearchEntryModel mod in model.Mods)
9697
{
@@ -283,27 +284,30 @@ private bool IsNewer(ISemanticVersion current, ISemanticVersion other)
283284
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
284285
private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions)
285286
{
286-
// get mod
287-
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))
287+
// get from cache
288+
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))
289+
return cachedMod.Data;
290+
291+
// fetch from mod site
288292
{
289293
// get site
290294
if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository))
291295
return new ModInfoModel().SetError(RemoteModStatus.DoesNotExist, $"There's no mod site with key '{updateKey.Repository}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}].");
292296

293297
// fetch mod
294-
ModInfoModel result = await repository.GetModInfoAsync(updateKey.ID);
295-
if (result.Error == null)
298+
ModInfoModel mod = await repository.GetModInfoAsync(updateKey.ID);
299+
if (mod.Error == null)
296300
{
297-
if (result.Version == null)
298-
result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number.");
299-
else if (!SemanticVersion.TryParse(result.Version, allowNonStandardVersions, out _))
300-
result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{result.Version}'.");
301+
if (mod.Version == null)
302+
mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number.");
303+
else if (!SemanticVersion.TryParse(mod.Version, allowNonStandardVersions, out _))
304+
mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{mod.Version}'.");
301305
}
302306

303307
// cache mod
304-
this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, result, out mod);
308+
this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, mod);
309+
return mod;
305310
}
306-
return mod.GetModel();
307311
}
308312

309313
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary>

src/SMAPI.Web/Controllers/ModsController.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Text.RegularExpressions;
33
using Microsoft.AspNetCore.Mvc;
44
using Microsoft.Extensions.Options;
5+
using StardewModdingAPI.Web.Framework.Caching;
56
using StardewModdingAPI.Web.Framework.Caching.Wiki;
67
using StardewModdingAPI.Web.Framework.ConfigModels;
78
using StardewModdingAPI.Web.ViewModels;
@@ -51,16 +52,16 @@ public ViewResult Index()
5152
public ModListModel FetchData()
5253
{
5354
// fetch cached data
54-
if (!this.Cache.TryGetWikiMetadata(out CachedWikiMetadata metadata))
55+
if (!this.Cache.TryGetWikiMetadata(out Cached<WikiMetadata> metadata))
5556
return new ModListModel();
5657

5758
// build model
5859
return new ModListModel(
59-
stableVersion: metadata.StableVersion,
60-
betaVersion: metadata.BetaVersion,
60+
stableVersion: metadata.Data.StableVersion,
61+
betaVersion: metadata.Data.BetaVersion,
6162
mods: this.Cache
6263
.GetWikiMods()
63-
.Select(mod => new ModModel(mod.GetModel()))
64+
.Select(mod => new ModModel(mod.Data))
6465
.OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting
6566
lastUpdated: metadata.LastUpdated,
6667
isStale: this.Cache.IsStale(metadata.LastUpdated, this.StaleMinutes)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
3+
namespace StardewModdingAPI.Web.Framework.Caching
4+
{
5+
/// <summary>A cache entry.</summary>
6+
/// <typeparam name="T">The cached value type.</typeparam>
7+
internal class Cached<T>
8+
{
9+
/*********
10+
** Accessors
11+
*********/
12+
/// <summary>The cached data.</summary>
13+
public T Data { get; set; }
14+
15+
/// <summary>When the data was last updated.</summary>
16+
public DateTimeOffset LastUpdated { get; set; }
17+
18+
/// <summary>When the data was last requested through the mod API.</summary>
19+
public DateTimeOffset LastRequested { get; set; }
20+
21+
22+
/*********
23+
** Public methods
24+
*********/
25+
/// <summary>Construct an empty instance.</summary>
26+
public Cached() { }
27+
28+
/// <summary>Construct an instance.</summary>
29+
/// <param name="data">The cached data.</param>
30+
public Cached(T data)
31+
{
32+
this.Data = data;
33+
this.LastUpdated = DateTimeOffset.UtcNow;
34+
this.LastRequested = DateTimeOffset.UtcNow;
35+
}
36+
}
37+
}

src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs

Lines changed: 0 additions & 107 deletions
This file was deleted.

src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ internal interface IModCacheRepository : ICacheRepository
1515
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
1616
/// <param name="mod">The fetched mod.</param>
1717
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
18-
bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true);
18+
bool TryGetMod(ModRepositoryKey site, string id, out Cached<ModInfoModel> mod, bool markRequested = true);
1919

2020
/// <summary>Save data fetched for a mod.</summary>
2121
/// <param name="site">The mod site on which the mod is found.</param>
2222
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
2323
/// <param name="mod">The mod data.</param>
24-
/// <param name="cachedMod">The stored mod record.</param>
25-
void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod);
24+
void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod);
2625

2726
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
2827
/// <param name="age">The minimum age for which to remove mods.</param>

src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal class ModCacheMemoryRepository : BaseCacheRepository, IModCacheReposito
1313
** Fields
1414
*********/
1515
/// <summary>The cached mod data indexed by <c>{site key}:{ID}</c>.</summary>
16-
private readonly IDictionary<string, CachedMod> Mods = new Dictionary<string, CachedMod>(StringComparer.InvariantCultureIgnoreCase);
16+
private readonly IDictionary<string, Cached<ModInfoModel>> Mods = new Dictionary<string, Cached<ModInfoModel>>(StringComparer.InvariantCultureIgnoreCase);
1717

1818

1919
/*********
@@ -24,31 +24,31 @@ internal class ModCacheMemoryRepository : BaseCacheRepository, IModCacheReposito
2424
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
2525
/// <param name="mod">The fetched mod.</param>
2626
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
27-
public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true)
27+
public bool TryGetMod(ModRepositoryKey site, string id, out Cached<ModInfoModel> mod, bool markRequested = true)
2828
{
2929
// get mod
30-
if (!this.Mods.TryGetValue(this.GetKey(site, id), out mod))
30+
if (!this.Mods.TryGetValue(this.GetKey(site, id), out var cachedMod))
31+
{
32+
mod = null;
3133
return false;
34+
}
3235

3336
// bump 'last requested'
3437
if (markRequested)
35-
{
36-
mod.LastRequested = DateTimeOffset.UtcNow;
37-
mod = this.SaveMod(mod);
38-
}
38+
cachedMod.LastRequested = DateTimeOffset.UtcNow;
3939

40+
mod = cachedMod;
4041
return true;
4142
}
4243

4344
/// <summary>Save data fetched for a mod.</summary>
4445
/// <param name="site">The mod site on which the mod is found.</param>
4546
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
4647
/// <param name="mod">The mod data.</param>
47-
/// <param name="cachedMod">The stored mod record.</param>
48-
public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod)
48+
public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod)
4949
{
5050
string key = this.GetKey(site, id);
51-
cachedMod = this.SaveMod(new CachedMod(site, id, mod));
51+
this.Mods[key] = new Cached<ModInfoModel>(mod);
5252
}
5353

5454
/// <summary>Delete data for mods which haven't been requested within a given time limit.</summary>
@@ -66,22 +66,14 @@ public void RemoveStaleMods(TimeSpan age)
6666
this.Mods.Remove(key);
6767
}
6868

69-
/// <summary>Save data fetched for a mod.</summary>
70-
/// <param name="mod">The mod data.</param>
71-
public CachedMod SaveMod(CachedMod mod)
72-
{
73-
string key = this.GetKey(mod.Site, mod.ID);
74-
return this.Mods[key] = mod;
75-
}
76-
7769

7870
/*********
7971
** Private methods
8072
*********/
8173
/// <summary>Get a cache key.</summary>
8274
/// <param name="site">The mod site.</param>
8375
/// <param name="id">The mod ID.</param>
84-
public string GetKey(ModRepositoryKey site, string id)
76+
private string GetKey(ModRepositoryKey site, string id)
8577
{
8678
return $"{site}:{id.Trim()}".ToLower();
8779
}

0 commit comments

Comments
 (0)