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 --- docs/technical/web.md | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) (limited to 'docs/technical') diff --git a/docs/technical/web.md b/docs/technical/web.md index 67e86c8b..ef591aee 100644 --- a/docs/technical/web.md +++ b/docs/technical/web.md @@ -340,9 +340,20 @@ short url | → | target page A local environment lets you run a complete copy of the web project (including cache database) on your machine, with no external dependencies aside from the actual mod sites. -1. Enter the Nexus credentials in `appsettings.Development.json` . You can leave the other - credentials empty to default to fetching data anonymously, and storing data in-memory and - on disk. +1. Edit `appsettings.Development.json` and set these options: + + property name | description + ------------- | ----------- + `NexusApiKey` | [Your Nexus API key](https://www.nexusmods.com/users/myaccount?tab=api#personal_key). + + Optional settings: + + property name | description + --------------------------- | ----------- + `AzureBlobConnectionString` | The connection string for the Azure Blob storage account. Defaults to using the system's temporary file folder if not specified. + `GitHubUsername`
`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. ### Production environment @@ -355,19 +366,15 @@ accordingly. Initial setup: -1. Launch an empty MongoDB server (e.g. using [MongoDB Atlas](https://www.mongodb.com/cloud/atlas)) - for mod data. -2. Create an Azure Blob storage account for uploaded files. -3. Create an Azure App Services environment running the latest .NET Core on Linux or Windows. -4. Add these application settings in the new App Services environment: +1. Create an Azure Blob storage account for uploaded files. +2. Create an Azure App Services environment running the latest .NET Core on Linux or Windows. +3. Add these application settings in the new App Services environment: property name | description ------------------------------- | ----------------- `ApiClients.AzureBlobConnectionString` | The connection string for the Azure Blob storage account created in step 2. `ApiClients.GitHubUsername`
`ApiClients.GitHubPassword` | The login credentials for the GitHub account with which to fetch release info. If these are omitted, GitHub will impose much stricter rate limits. `ApiClients:NexusApiKey` | The [Nexus API authentication key](https://github.com/Pathoschild/FluentNexus#init-a-client). - `MongoDB:ConnectionString` | The connection string for the MongoDB instance. - `MongoDB:Database` | The MongoDB database name (e.g. `smapi` in production or `smapi-edge` in testing environments). Optional settings: @@ -378,6 +385,23 @@ 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.) -- 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. --- docs/release-notes.md | 2 +- docs/technical/web.md | 22 +- src/SMAPI.Web/BackgroundService.cs | 2 +- src/SMAPI.Web/Controllers/ModsApiController.cs | 26 ++- src/SMAPI.Web/Controllers/ModsController.cs | 9 +- 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 -- src/SMAPI.Web/SMAPI.Web.csproj | 3 - src/SMAPI.Web/Startup.cs | 78 +------ src/SMAPI.Web/appsettings.Development.json | 6 - 22 files changed, 123 insertions(+), 793 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 'docs/technical') diff --git a/docs/release-notes.md b/docs/release-notes.md index 4596a525..f3f2efa4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -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 diff --git a/docs/technical/web.md b/docs/technical/web.md index ef591aee..d21b87ac 100644 --- a/docs/technical/web.md +++ b/docs/technical/web.md @@ -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`
`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). diff --git a/src/SMAPI.Web/BackgroundService.cs b/src/SMAPI.Web/BackgroundService.cs index 275622fe..64bd5ca5 100644 --- a/src/SMAPI.Web/BackgroundService.cs +++ b/src/SMAPI.Web/BackgroundService.cs @@ -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); } /// Remove mods which haven't been requested in over 48 hours. diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 6032186f..b9d7c32d 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -12,6 +12,7 @@ using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Framework.UpdateData; using StardewModdingAPI.Web.Framework; +using StardewModdingAPI.Web.Framework.Caching; using StardewModdingAPI.Web.Framework.Caching.Mods; using StardewModdingAPI.Web.Framework.Caching.Wiki; using StardewModdingAPI.Web.Framework.Clients.Chucklefish; @@ -90,7 +91,7 @@ namespace StardewModdingAPI.Web.Controllers return new ModEntryModel[0]; // fetch wiki data - WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.GetModel()).ToArray(); + WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.Data).ToArray(); IDictionary mods = new Dictionary(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in model.Mods) { @@ -283,27 +284,30 @@ namespace StardewModdingAPI.Web.Controllers /// Whether to allow non-standard versions. private async Task GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions) { - // get mod - if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) + // get from cache + if (this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out Cached cachedMod) && !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) + return cachedMod.Data; + + // fetch from mod site { // get site if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository)) return new ModInfoModel().SetError(RemoteModStatus.DoesNotExist, $"There's no mod site with key '{updateKey.Repository}'. Expected one of [{string.Join(", ", this.Repositories.Keys)}]."); // fetch mod - ModInfoModel result = await repository.GetModInfoAsync(updateKey.ID); - if (result.Error == null) + ModInfoModel mod = await repository.GetModInfoAsync(updateKey.ID); + if (mod.Error == null) { - if (result.Version == null) - result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); - else if (!SemanticVersion.TryParse(result.Version, allowNonStandardVersions, out _)) - result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{result.Version}'."); + if (mod.Version == null) + mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); + else if (!SemanticVersion.TryParse(mod.Version, allowNonStandardVersions, out _)) + mod.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{mod.Version}'."); } // cache mod - this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, result, out mod); + this.ModCache.SaveMod(repository.VendorKey, updateKey.ID, mod); + return mod; } - return mod.GetModel(); } /// Get update keys based on the available mod metadata, while maintaining the precedence order. diff --git a/src/SMAPI.Web/Controllers/ModsController.cs b/src/SMAPI.Web/Controllers/ModsController.cs index b621ded0..24e36709 100644 --- a/src/SMAPI.Web/Controllers/ModsController.cs +++ b/src/SMAPI.Web/Controllers/ModsController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using StardewModdingAPI.Web.Framework.Caching; using StardewModdingAPI.Web.Framework.Caching.Wiki; using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.ViewModels; @@ -51,16 +52,16 @@ namespace StardewModdingAPI.Web.Controllers public ModListModel FetchData() { // fetch cached data - if (!this.Cache.TryGetWikiMetadata(out CachedWikiMetadata metadata)) + if (!this.Cache.TryGetWikiMetadata(out Cached metadata)) return new ModListModel(); // build model return new ModListModel( - stableVersion: metadata.StableVersion, - betaVersion: metadata.BetaVersion, + stableVersion: metadata.Data.StableVersion, + betaVersion: metadata.Data.BetaVersion, mods: this.Cache .GetWikiMods() - .Select(mod => new ModModel(mod.GetModel())) + .Select(mod => new ModModel(mod.Data)) .OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting lastUpdated: metadata.LastUpdated, isStale: this.Cache.IsStale(metadata.LastUpdated, this.StaleMinutes) 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); - } + cachedMod.LastRequested = DateTimeOffset.UtcNow; + mod = cachedMod; return true; } @@ -44,11 +45,10 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods /// 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) + 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(mod); } /// Delete data for mods which haven't been requested within a given time limit. @@ -66,14 +66,6 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods 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 @@ -81,7 +73,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods /// Get a cache key. /// The mod site. /// The mod ID. - public string GetKey(ModRepositoryKey site, string id) + private 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 deleted file mode 100644 index f105baab..00000000 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMongoRepository.cs +++ /dev/null @@ -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 -{ - /// 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/UtcDateTimeOffsetSerializer.cs b/src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs deleted file mode 100644 index 6a103e37..00000000 --- a/src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Serializers; - -namespace StardewModdingAPI.Web.Framework.Caching -{ - /// Serializes to a UTC date field instead of the default array. - public class UtcDateTimeOffsetSerializer : StructSerializerBase - { - /********* - ** Fields - *********/ - /// The underlying date serializer. - private static readonly DateTimeSerializer DateTimeSerializer = new DateTimeSerializer(DateTimeKind.Utc, BsonType.DateTime); - - - /********* - ** Public methods - *********/ - /// Deserializes a value. - /// The deserialization context. - /// The deserialization args. - /// A deserialized value. - public override DateTimeOffset Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) - { - DateTime date = UtcDateTimeOffsetSerializer.DateTimeSerializer.Deserialize(context, args); - return new DateTimeOffset(date, TimeSpan.Zero); - } - - /// Serializes a value. - /// The serialization context. - /// The serialization args. - /// The object. - public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTimeOffset value) - { - UtcDateTimeOffsetSerializer.DateTimeSerializer.Serialize(context, args, value.UtcDateTime); - } - } -} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs deleted file mode 100644 index 6a560eb4..00000000 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using MongoDB.Bson; - -namespace StardewModdingAPI.Web.Framework.Caching.Wiki -{ - /// The model for cached wiki metadata. - internal class CachedWikiMetadata - { - /********* - ** Accessors - *********/ - /// The internal MongoDB ID. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")] - public ObjectId _id { get; set; } - - /// When the data was last updated. - public DateTimeOffset LastUpdated { get; set; } - - /// The current stable Stardew Valley version. - public string StableVersion { get; set; } - - /// The current beta Stardew Valley version. - public string BetaVersion { get; set; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public CachedWikiMetadata() { } - - /// Construct an instance. - /// The current stable Stardew Valley version. - /// The current beta Stardew Valley version. - public CachedWikiMetadata(string stableVersion, string betaVersion) - { - this.StableVersion = stableVersion; - this.BetaVersion = betaVersion; - this.LastUpdated = DateTimeOffset.UtcNow; - } - } -} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs deleted file mode 100644 index 7e7c99bc..00000000 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs +++ /dev/null @@ -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 -{ - /// The model for cached wiki mods. - internal class CachedWikiMod - { - /********* - ** Accessors - *********/ - /**** - ** Tracking - ****/ - /// The internal MongoDB ID. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")] - public ObjectId _id { get; set; } - - /// When the data was last updated. - public DateTimeOffset LastUpdated { get; set; } - - /**** - ** Mod info - ****/ - /// The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order. - public string[] ID { get; set; } - - /// The mod's display name. If the mod has multiple names, the first one is the most canonical name. - public string[] Name { get; set; } - - /// The mod's author name. If the author has multiple names, the first one is the most canonical name. - public string[] Author { get; set; } - - /// The mod ID on Nexus. - public int? NexusID { get; set; } - - /// The mod ID in the Chucklefish mod repo. - public int? ChucklefishID { get; set; } - - /// The mod ID in the CurseForge mod repo. - public int? CurseForgeID { get; set; } - - /// The mod key in the CurseForge mod repo (used in mod page URLs). - public string CurseForgeKey { get; set; } - - /// The mod ID in the ModDrop mod repo. - public int? ModDropID { get; set; } - - /// The GitHub repository in the form 'owner/repo'. - public string GitHubRepo { get; set; } - - /// The URL to a non-GitHub source repo. - public string CustomSourceUrl { get; set; } - - /// The custom mod page URL (if applicable). - public string CustomUrl { get; set; } - - /// The name of the mod which loads this content pack, if applicable. - public string ContentPackFor { get; set; } - - /// The human-readable warnings for players about this mod. - public string[] Warnings { get; set; } - - /// The URL of the pull request which submits changes for an unofficial update to the author, if any. - public string PullRequestUrl { get; set; } - - /// Special notes intended for developers who maintain unofficial updates or submit pull requests. - public string DevNote { get; set; } - - /// The link anchor for the mod entry in the wiki compatibility list. - public string Anchor { get; set; } - - /**** - ** Stable compatibility - ****/ - /// The compatibility status. - public WikiCompatibilityStatus MainStatus { get; set; } - - /// The human-readable summary of the compatibility status or workaround, without HTML formatting. - public string MainSummary { get; set; } - - /// The game or SMAPI version which broke this mod (if applicable). - public string MainBrokeIn { get; set; } - - /// The version of the latest unofficial update, if applicable. - public string MainUnofficialVersion { get; set; } - - /// The URL to the latest unofficial update, if applicable. - public string MainUnofficialUrl { get; set; } - - /**** - ** Beta compatibility - ****/ - /// The compatibility status. - public WikiCompatibilityStatus? BetaStatus { get; set; } - - /// The human-readable summary of the compatibility status or workaround, without HTML formatting. - public string BetaSummary { get; set; } - - /// The game or SMAPI version which broke this mod (if applicable). - public string BetaBrokeIn { get; set; } - - /// The version of the latest unofficial update, if applicable. - public string BetaUnofficialVersion { get; set; } - - /// The URL to the latest unofficial update, if applicable. - public string BetaUnofficialUrl { get; set; } - - /**** - ** Version maps - ****/ - /// Maps local versions to a semantic version for update checks. - [BsonDictionaryOptions(Representation = DictionaryRepresentation.ArrayOfArrays)] - public IDictionary MapLocalVersions { get; set; } - - /// Maps remote versions to a semantic version for update checks. - [BsonDictionaryOptions(Representation = DictionaryRepresentation.ArrayOfArrays)] - public IDictionary MapRemoteVersions { get; set; } - - - /********* - ** Accessors - *********/ - /// Construct an instance. - public CachedWikiMod() { } - - /// Construct an instance. - /// The mod data. - 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; - } - - /// Reconstruct the original model. - 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; - } - } -} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs index 02097f52..2ab7ea5a 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs @@ -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 *********/ /// Get the cached wiki metadata. /// The fetched metadata. - bool TryGetWikiMetadata(out CachedWikiMetadata metadata); + bool TryGetWikiMetadata(out Cached metadata); /// Get the cached wiki mods. /// A filter to apply, if any. - IEnumerable GetWikiMods(Expression> filter = null); + IEnumerable> GetWikiMods(Func filter = null); /// 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. - void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods); + void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods); } } diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs index 4621f5e3..064a7c3c 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs @@ -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 *********/ /// The saved wiki metadata. - private CachedWikiMetadata Metadata; + private Cached Metadata; /// The cached wiki data. - private CachedWikiMod[] Mods = new CachedWikiMod[0]; + private Cached[] Mods = new Cached[0]; /********* @@ -24,7 +23,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki *********/ /// Get the cached wiki metadata. /// The fetched metadata. - public bool TryGetWikiMetadata(out CachedWikiMetadata metadata) + public bool TryGetWikiMetadata(out Cached metadata) { metadata = this.Metadata; return metadata != null; @@ -32,23 +31,23 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki /// Get the cached wiki mods. /// A filter to apply, if any. - public IEnumerable GetWikiMods(Expression> filter = null) + public IEnumerable> GetWikiMods(Func 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; + } } /// 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) + public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods) { - this.Metadata = cachedMetadata = new CachedWikiMetadata(stableVersion, betaVersion); - this.Mods = cachedMods = mods.Select(mod => new CachedWikiMod(mod)).ToArray(); + this.Metadata = new Cached(new WikiMetadata(stableVersion, betaVersion)); + this.Mods = mods.Select(mod => new Cached(mod)).ToArray(); } } } diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.cs deleted file mode 100644 index 07e7c721..00000000 --- a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMongoRepository.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 -{ - /// 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/WikiMetadata.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs new file mode 100644 index 00000000..c04de4a5 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs @@ -0,0 +1,31 @@ +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// The model for cached wiki metadata. + internal class WikiMetadata + { + /********* + ** Accessors + *********/ + /// The current stable Stardew Valley version. + public string StableVersion { get; set; } + + /// The current beta Stardew Valley version. + public string BetaVersion { get; set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public WikiMetadata() { } + + /// Construct an instance. + /// The current stable Stardew Valley version. + /// The current beta Stardew Valley version. + public WikiMetadata(string stableVersion, string betaVersion) + { + this.StableVersion = stableVersion; + this.BetaVersion = betaVersion; + } + } +} diff --git a/src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs deleted file mode 100644 index 61cc4855..00000000 --- a/src/SMAPI.Web/Framework/ConfigModels/StorageConfig.cs +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 4c2ea801..00000000 --- a/src/SMAPI.Web/Framework/ConfigModels/StorageMode.cs +++ /dev/null @@ -1,15 +0,0 @@ -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 - } -} diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 7ed79ea3..c6c0f774 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -15,14 +15,11 @@ - - - diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index dee2edc2..586b0c3c 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -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(this.Configuration.GetSection("BackgroundServices")) .Configure(this.Configuration.GetSection("ModCompatibilityList")) .Configure(this.Configuration.GetSection("ModUpdateCheck")) - .Configure(this.Configuration.GetSection("Storage")) .Configure(this.Configuration.GetSection("Site")) .Configure(options => options.ConstraintMap.Add("semanticVersion", typeof(VersionConstraint))) .AddLogging() .AddMemoryCache(); - StorageConfig storageConfig = this.Configuration.GetSection("Storage").Get(); - StorageMode storageMode = storageConfig.Mode; // init MVC services @@ -85,39 +76,8 @@ namespace StardewModdingAPI.Web .AddRazorPages(); // init storage - switch (storageMode) - { - case StorageMode.InMemory: - services.AddSingleton(new ModCacheMemoryRepository()); - services.AddSingleton(new WikiCacheMemoryRepository()); - break; - - case StorageMode.Mongo: - case StorageMode.MongoInMemory: - { - // local MongoDB instance - services.AddSingleton(_ => storageMode == StorageMode.MongoInMemory - ? MongoDbRunner.Start() - : throw new NotSupportedException($"The in-memory MongoDB runner isn't available in storage mode {storageMode}.") - ); - - // MongoDB - services.AddSingleton(serv => - { - BsonSerializer.RegisterSerializer(new UtcDateTimeOffsetSerializer()); - return new MongoClient(this.GetMongoDbConnectionString(serv, storageConfig)) - .GetDatabase(storageConfig.Database); - }); - - // repositories - services.AddSingleton(serv => new ModCacheMongoRepository(serv.GetRequiredService())); - services.AddSingleton(serv => new WikiCacheMongoRepository(serv.GetRequiredService())); - } - break; - - default: - throw new NotSupportedException($"Unhandled storage mode '{storageMode}'."); - } + services.AddSingleton(new ModCacheMemoryRepository()); + services.AddSingleton(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; } - /// Get the MongoDB connection string for the given storage configuration. - /// The service provider. - /// The storage configuration - /// There's no MongoDB instance in the given storage mode. - private string GetMongoDbConnectionString(IServiceProvider services, StorageConfig storageConfig) - { - return storageConfig.Mode switch - { - StorageMode.Mongo => storageConfig.ConnectionString, - StorageMode.MongoInMemory => services.GetRequiredService().ConnectionString, - _ => throw new NotSupportedException($"There's no MongoDB instance in storage mode {storageConfig.Mode}.") - }; - } - /// Get the redirect rules to apply. private RewriteOptions GetRedirectRules() { diff --git a/src/SMAPI.Web/appsettings.Development.json b/src/SMAPI.Web/appsettings.Development.json index 41c00e79..3aa69285 100644 --- a/src/SMAPI.Web/appsettings.Development.json +++ b/src/SMAPI.Web/appsettings.Development.json @@ -17,12 +17,6 @@ "NexusApiKey": null }, - "Storage": { - "Mode": "MongoInMemory", - "ConnectionString": null, - "Database": "smapi-edge" - }, - "BackgroundServices": { "Enabled": true } -- cgit From 2d19095169019a1cb07da5802dd83fb13550a051 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 14 Jun 2020 11:29:07 -0400 Subject: add support for using a custom Harmony build (#711) --- .gitignore | 4 ++-- docs/technical/smapi.md | 33 ++++++++++++++++++++------------- src/SMAPI/SMAPI.csproj | 3 ++- 3 files changed, 24 insertions(+), 16 deletions(-) (limited to 'docs/technical') diff --git a/.gitignore b/.gitignore index 5450a2f5..02522716 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,8 @@ _ReSharper*/ # sensitive files appsettings.Development.json -# AWS generated files -src/SMAPI.Web.LegacyRedirects/aws-beanstalk-tools-defaults.json +# generated build files +build/0Harmony.* # Azure generated files src/SMAPI.Web/Properties/PublishProfiles/*.pubxml diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index c9d5c07e..ca8a9c70 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -15,6 +15,7 @@ This document is about SMAPI itself; see also [mod build package](mod-package.md * [Compiling from source](#compiling-from-source) * [Debugging a local build](#debugging-a-local-build) * [Preparing a release](#preparing-a-release) + * [Using a custom Harmony build](#using-a-custom-harmony-build) * [Release notes](#release-notes) ## Customisation @@ -60,21 +61,18 @@ flag | purpose ## For SMAPI developers ### Compiling from source -Using an official SMAPI release is recommended for most users. +Using an official SMAPI release is recommended for most users, but you can compile from source +directly if needed. There are no special steps (just open the project and compile), but SMAPI often +uses the latest C# syntax. You may need the latest version of your IDE to compile it. -SMAPI often uses the latest C# syntax. You may need the latest version of -[Visual Studio](https://www.visualstudio.com/vs/community/) on Windows, -[MonoDevelop](https://www.monodevelop.com/) on Linux, -[Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/), or an equivalent IDE -to compile it. It uses build configuration derived from the -[crossplatform mod config](https://smapi.io/package/readme) to detect your current OS automatically -and load the correct references. Compile output will be placed in a `bin` folder at the root of the -git repository. +SMAPI uses build configuration derived from the [crossplatform mod config](https://smapi.io/package/readme) +to detect your current OS automatically and load the correct references. Compile output will be +placed in a `bin` folder at the root of the Git repository. ### Debugging a local build Rebuilding the solution in debug mode will copy the SMAPI files into your game folder. Starting the `SMAPI` project with debugging from Visual Studio (on Mac or Windows) will launch SMAPI with -the debugger attached, so you can intercept errors and step through the code being executed. This +the debugger attached, so you can intercept errors and step through the code being executed. That doesn't work in MonoDevelop on Linux, unfortunately. ### Preparing a release @@ -87,9 +85,9 @@ on the wiki for the first-time setup. build type | format | example :--------- | :----------------------- | :------ - dev build | `-alpha.` | `3.0-alpha.20171230` - prerelease | `-beta.` | `3.0-beta.2` - release | `` | `3.0` + dev build | `-alpha.` | `3.0.0-alpha.20171230` + prerelease | `-beta.` | `3.0.0-beta.2` + release | `` | `3.0.0` 2. In Windows: 1. Rebuild the solution in Release mode. @@ -103,5 +101,14 @@ on the wiki for the first-time setup. 3. Rename the folders to `SMAPI installer` and `SMAPI installer for developers`. 4. Zip the two folders. +### Using a custom Harmony build +The official SMAPI releases include [a custom build of Harmony](https://github.com/Pathoschild/Harmony), +but compiling from source will use the official build. To use a custom build, put `0Harmony.dll` in +the `build` folder and it'll be referenced automatically. + +Note that Harmony merges its dependencies into `0Harmony.dll` when compiled in release mode. To use +a debug build of Harmony, you'll need to manually copy those dependencies into your game's +`smapi-internal` folder. + ## Release notes See [release notes](../release-notes.md). diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 02f1763f..443d5baa 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -14,13 +14,14 @@ - + + -- cgit From dcd2c647a2abd836e8ee20f8ddad6568c9b4fbf2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 15 Jun 2020 22:17:32 -0400 Subject: temporarily restore Harmony 1.x support with compile flag (#711) --- docs/release-notes.md | 10 ++- docs/technical/smapi.md | 1 + .../Framework/Commands/HarmonySummaryCommand.cs | 2 + .../ModLoading/RewriteFacades/AccessToolsFacade.cs | 2 + .../RewriteFacades/HarmonyInstanceFacade.cs | 2 + .../RewriteFacades/HarmonyMethodFacade.cs | 2 + .../Rewriters/Harmony1AssemblyRewriter.cs | 2 + src/SMAPI/Framework/Patching/GamePatcher.cs | 8 ++ src/SMAPI/Framework/Patching/IHarmonyPatch.cs | 8 ++ src/SMAPI/Framework/Patching/PatchHelper.cs | 36 +++++++++ src/SMAPI/Framework/SCore.cs | 2 + src/SMAPI/Metadata/InstructionMetadata.cs | 6 ++ src/SMAPI/Patches/DialogueErrorPatch.cs | 94 +++++++++++++++++++++- src/SMAPI/Patches/EventErrorPatch.cs | 48 ++++++++++- src/SMAPI/Patches/LoadContextPatch.cs | 8 ++ src/SMAPI/Patches/LoadErrorPatch.cs | 8 ++ src/SMAPI/Patches/ObjectErrorPatch.cs | 50 +++++++++++- src/SMAPI/Patches/ScheduleErrorPatch.cs | 50 +++++++++++- src/SMAPI/SMAPI.csproj | 2 +- 19 files changed, 328 insertions(+), 13 deletions(-) create mode 100644 src/SMAPI/Framework/Patching/PatchHelper.cs (limited to 'docs/technical') diff --git a/docs/release-notes.md b/docs/release-notes.md index dd87c1fc..c47ee835 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,12 @@ ← [README](README.md) # Release notes -## Upcoming released +## Upcoming release + 1 +* For modders: + * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). + * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter. + +## Upcoming release * For players: * Mod warnings are now listed alphabetically. * MacOS files starting with `._` are now ignored and can no longer cause skipped mods. @@ -17,12 +22,10 @@ * Internal changes to improve performance and reliability. * For modders: - * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). * Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!). * Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys). * Added `Multiplayer.PeerConnected` event. * Added ability to override update keys from the compatibility list. - * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter. * Harmony mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility. * Improved mod rewriting for compatibility: * Fixed rewriting types in custom attributes. @@ -33,6 +36,7 @@ * Fixed `ModMessageReceived` event handlers not tracked for performance monitoring. * For SMAPI developers: + * Added support for bundling a custom Harmony build for upcoming use. * 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. * Overhauled update checks to simplify individual clients, centralize common logic, and enable upcoming features. * Merged the separate legacy redirects app on AWS into the main app on Azure. diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index ca8a9c70..3b2d6e56 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -58,6 +58,7 @@ SMAPI uses a small number of conditional compilation constants, which you can se flag | purpose ---- | ------- `SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`. +`HARMONY_2` | Whether to enable experimental Harmony 2.0 support. Existing Harmony 1._x_ mods will be rewritten automatically for compatibility. ## For SMAPI developers ### Compiling from source diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index 08233feb..8c20fbdd 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Collections.Generic; using System.Linq; @@ -166,3 +167,4 @@ namespace StardewModdingAPI.Framework.Commands } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index 8e4320b3..102f3364 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -40,3 +41,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index 54b91679..ad6d5e4f 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -80,3 +81,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs index 44c97401..f3975558 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -43,3 +44,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index 8fed170a..b30d686e 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using HarmonyLib; using Mono.Cecil; @@ -125,3 +126,4 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } } } +#endif diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs index dcad285a..82d7b9c8 100644 --- a/src/SMAPI/Framework/Patching/GamePatcher.cs +++ b/src/SMAPI/Framework/Patching/GamePatcher.cs @@ -1,5 +1,9 @@ using System; +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif namespace StardewModdingAPI.Framework.Patching { @@ -27,7 +31,11 @@ namespace StardewModdingAPI.Framework.Patching /// The patches to apply. public void Apply(params IHarmonyPatch[] patches) { +#if HARMONY_2 Harmony harmony = new Harmony("SMAPI"); +#else + HarmonyInstance harmony = HarmonyInstance.Create("SMAPI"); +#endif foreach (IHarmonyPatch patch in patches) { try diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs index 7d5eb3d4..922243fa 100644 --- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs +++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs @@ -1,4 +1,8 @@ +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif namespace StardewModdingAPI.Framework.Patching { @@ -10,6 +14,10 @@ namespace StardewModdingAPI.Framework.Patching /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 void Apply(Harmony harmony); +#else + void Apply(HarmonyInstance harmony); +#endif } } diff --git a/src/SMAPI/Framework/Patching/PatchHelper.cs b/src/SMAPI/Framework/Patching/PatchHelper.cs new file mode 100644 index 00000000..d1aa0185 --- /dev/null +++ b/src/SMAPI/Framework/Patching/PatchHelper.cs @@ -0,0 +1,36 @@ +#if !HARMONY_2 +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework.Patching +{ + /// Provides generic methods for implementing Harmony patches. + internal class PatchHelper + { + /********* + ** Fields + *********/ + /// The interception keys currently being intercepted. + private static readonly HashSet InterceptingKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + + /********* + ** Public methods + *********/ + /// Track a method that will be intercepted. + /// The intercept key. + /// Returns false if the method was already marked for interception, else true. + public static bool StartIntercept(string key) + { + return PatchHelper.InterceptingKeys.Add(key); + } + + /// Track a method as no longer being intercepted. + /// The intercept key. + public static void StopIntercept(string key) + { + PatchHelper.InterceptingKeys.Remove(key); + } + } +} +#endif diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 530b6754..1a2c97f4 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -511,7 +511,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Type 'help' for help, or 'help ' for a command's usage", LogLevel.Info); this.GameInstance.CommandManager .Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor) +#if HARMONY_2 .Add(new HarmonySummaryCommand(), this.Monitor) +#endif .Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor); // start handling command line input diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 89430a11..79d7a7a8 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -38,8 +38,10 @@ namespace StardewModdingAPI.Metadata // rewrite for Stardew Valley 1.3 yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); +#if HARMONY_2 // rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update) yield return new Harmony1AssemblyRewriter(); +#endif /**** ** detect mod issues @@ -51,7 +53,11 @@ namespace StardewModdingAPI.Metadata /**** ** detect code which may impact game stability ****/ +#if HARMONY_2 yield return new TypeFinder(typeof(HarmonyLib.Harmony).FullName, InstructionHandleResult.DetectedGamePatch); +#else + yield return new TypeFinder(typeof(Harmony.HarmonyInstance).FullName, InstructionHandleResult.DetectedGamePatch); +#endif yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic); yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerializer); yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerializer); diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs index cddf29d6..8043eda3 100644 --- a/src/SMAPI/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI/Patches/DialogueErrorPatch.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; using StardewValley; +#if HARMONY_2 +using HarmonyLib; +using StardewModdingAPI.Framework; +#else +using System.Reflection; +using Harmony; +#endif namespace StardewModdingAPI.Patches { @@ -47,6 +52,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) { harmony.Patch( @@ -58,11 +64,24 @@ namespace StardewModdingAPI.Patches finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_NPC_CurrentDialogue)) ); } - +#else + public void Apply(HarmonyInstance harmony) + { + harmony.Patch( + original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }), + prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_Dialogue_Constructor)) + ); + harmony.Patch( + original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod, + prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue)) + ); + } +#endif /********* ** Private methods *********/ +#if HARMONY_2 /// The method to call after the Dialogue constructor. /// The instance being patched. /// The dialogue being parsed. @@ -102,5 +121,74 @@ namespace StardewModdingAPI.Patches return null; } +#else + + /// The method to call instead of the Dialogue constructor. + /// The instance being patched. + /// The dialogue being parsed. + /// The NPC for which the dialogue is being parsed. + /// Returns whether to execute the original method. + private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker) + { + // get private members + bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField(typeof(Dialogue), "nameArraysTranslated").GetValue(); + IReflectedMethod translateArraysOfStrings = DialogueErrorPatch.Reflection.GetMethod(typeof(Dialogue), "TranslateArraysOfStrings"); + IReflectedMethod parseDialogueString = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString"); + IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes"); + IReflectedField> dialogues = DialogueErrorPatch.Reflection.GetField>(__instance, "dialogues"); + + // replicate base constructor + if (dialogues.GetValue() == null) + dialogues.SetValue(new List()); + + // duplicate code with try..catch + try + { + if (!nameArraysTranslated) + translateArraysOfStrings.Invoke(); + __instance.speaker = speaker; + parseDialogueString.Invoke(masterDialogue); + checkForSpecialDialogueAttributes.Invoke(); + } + catch (Exception baseEx) when (baseEx.InnerException is TargetInvocationException invocationEx && invocationEx.InnerException is Exception ex) + { + string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null; + DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{ex}", LogLevel.Error); + + parseDialogueString.Invoke("..."); + checkForSpecialDialogueAttributes.Invoke(); + } + + return false; + } + + /// The method to call instead of . + /// The instance being patched. + /// The return value of the original method. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack __result, MethodInfo __originalMethod) + { + const string key = nameof(Before_NPC_CurrentDialogue); + if (!PatchHelper.StartIntercept(key)) + return true; + + try + { + __result = (Stack)__originalMethod.Invoke(__instance, new object[0]); + return false; + } + catch (TargetInvocationException ex) + { + DialogueErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{ex.InnerException ?? ex}", LogLevel.Error); + __result = new Stack(); + return false; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif } } diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs index de9dea29..4dbb25f3 100644 --- a/src/SMAPI/Patches/EventErrorPatch.cs +++ b/src/SMAPI/Patches/EventErrorPatch.cs @@ -1,6 +1,11 @@ -using System; using System.Diagnostics.CodeAnalysis; +#if HARMONY_2 +using System; using HarmonyLib; +#else +using System.Reflection; +using Harmony; +#endif using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -38,6 +43,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) { harmony.Patch( @@ -45,11 +51,21 @@ namespace StardewModdingAPI.Patches finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition)) ); } +#else + public void Apply(HarmonyInstance harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"), + prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition)) + ); + } +#endif /********* ** Private methods *********/ +#if HARMONY_2 /// The method to call instead of the GameLocation.CheckEventPrecondition. /// The return value of the original method. /// The precondition to be parsed. @@ -65,5 +81,35 @@ namespace StardewModdingAPI.Patches return null; } +#else + /// The method to call instead of the GameLocation.CheckEventPrecondition. + /// The instance being patched. + /// The return value of the original method. + /// The precondition to be parsed. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) + { + const string key = nameof(Before_GameLocation_CheckEventPrecondition); + if (!PatchHelper.StartIntercept(key)) + return true; + + try + { + __result = (int)__originalMethod.Invoke(__instance, new object[] { precondition }); + return false; + } + catch (TargetInvocationException ex) + { + __result = -1; + EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error); + return false; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif } } diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index 9c707676..768ddd6b 100644 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -1,6 +1,10 @@ using System; using System.Diagnostics.CodeAnalysis; +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif using StardewModdingAPI.Enums; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; @@ -47,7 +51,11 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { // detect CreatedBasicInfo harmony.Patch( diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs index f8ad6693..5e67b169 100644 --- a/src/SMAPI/Patches/LoadErrorPatch.cs +++ b/src/SMAPI/Patches/LoadErrorPatch.cs @@ -2,7 +2,11 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -49,7 +53,11 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { harmony.Patch( original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs index 189a14a0..4edcc64e 100644 --- a/src/SMAPI/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI/Patches/ObjectErrorPatch.cs @@ -1,11 +1,16 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; using StardewValley.Menus; using SObject = StardewValley.Object; +#if HARMONY_2 +using System; +using HarmonyLib; +#else +using System.Reflection; +using Harmony; +#endif namespace StardewModdingAPI.Patches { @@ -27,7 +32,11 @@ namespace StardewModdingAPI.Patches *********/ /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { // object.getDescription harmony.Patch( @@ -38,7 +47,11 @@ namespace StardewModdingAPI.Patches // object.getDisplayName harmony.Patch( original: AccessTools.Method(typeof(SObject), "loadDisplayName"), +#if HARMONY_2 finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName)) +#else + prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_loadDisplayName)) +#endif ); // IClickableMenu.drawToolTip @@ -68,6 +81,7 @@ namespace StardewModdingAPI.Patches return true; } +#if HARMONY_2 /// The method to call after . /// The patched method's return value. /// The exception thrown by the wrapped method, if any. @@ -82,6 +96,38 @@ namespace StardewModdingAPI.Patches return __exception; } +#else + /// The method to call instead of . + /// The instance being patched. + /// The patched method's return value. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod) + { + const string key = nameof(Before_Object_loadDisplayName); + if (!PatchHelper.StartIntercept(key)) + return true; + + try + { + __result = (string)__originalMethod.Invoke(__instance, new object[0]); + return false; + } + catch (TargetInvocationException ex) when (ex.InnerException is KeyNotFoundException) + { + __result = "???"; + return false; + } + catch + { + return true; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif /// The method to call instead of . /// The item for which to draw a tooltip. diff --git a/src/SMAPI/Patches/ScheduleErrorPatch.cs b/src/SMAPI/Patches/ScheduleErrorPatch.cs index df6ffab3..cc2238b0 100644 --- a/src/SMAPI/Patches/ScheduleErrorPatch.cs +++ b/src/SMAPI/Patches/ScheduleErrorPatch.cs @@ -1,10 +1,15 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Patching; using StardewValley; +#if HARMONY_2 +using System; +using HarmonyLib; +using StardewModdingAPI.Framework; +#else +using System.Reflection; +using Harmony; +#endif namespace StardewModdingAPI.Patches { @@ -40,11 +45,19 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { harmony.Patch( original: AccessTools.Method(typeof(NPC), "parseMasterSchedule"), +#if HARMONY_2 finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule)) +#else + prefix: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule)) +#endif ); } @@ -52,6 +65,7 @@ namespace StardewModdingAPI.Patches /********* ** Private methods *********/ +#if HARMONY_2 /// The method to call instead of . /// The raw schedule data to parse. /// The instance being patched. @@ -68,5 +82,35 @@ namespace StardewModdingAPI.Patches return null; } +#else + /// The method to call instead of . + /// The raw schedule data to parse. + /// The instance being patched. + /// The patched method's return value. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, MethodInfo __originalMethod) + { + const string key = nameof(Before_NPC_parseMasterSchedule); + if (!PatchHelper.StartIntercept(key)) + return true; + + try + { + __result = (Dictionary)__originalMethod.Invoke(__instance, new object[] { rawData }); + return false; + } + catch (TargetInvocationException ex) + { + ScheduleErrorPatch.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{ex.InnerException ?? ex}", LogLevel.Error); + __result = new Dictionary(); + return false; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif } } diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 603b6fb5..c17de6d0 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -14,7 +14,7 @@ - + -- cgit From a7cf886b7145e94dbcb1157f73aa0f2922fa183f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Jun 2020 00:13:23 -0400 Subject: switch to custom build of Harmony 1.2.0.1 --- .gitignore | 3 --- build/0Harmony.dll | Bin 0 -> 115200 bytes docs/release-notes.md | 1 + docs/technical/smapi.md | 14 +++++--------- src/SMAPI/SMAPI.csproj | 3 +-- 5 files changed, 7 insertions(+), 14 deletions(-) create mode 100644 build/0Harmony.dll (limited to 'docs/technical') diff --git a/.gitignore b/.gitignore index 02522716..b7f0d3e6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,5 @@ _ReSharper*/ # sensitive files appsettings.Development.json -# generated build files -build/0Harmony.* - # Azure generated files src/SMAPI.Web/Properties/PublishProfiles/*.pubxml diff --git a/build/0Harmony.dll b/build/0Harmony.dll new file mode 100644 index 00000000..2e893d0e Binary files /dev/null and b/build/0Harmony.dll differ diff --git a/docs/release-notes.md b/docs/release-notes.md index 9ea3e445..eadd45eb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -28,6 +28,7 @@ * Added `Multiplayer.PeerConnected` event. * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter. * Added ability to override update keys from the compatibility list. + * SMAPI now uses [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme) to provide more useful stack traces in error logs. * Harmony mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility. * Improved mod rewriting for compatibility: * Fixed rewriting types in custom attributes. diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index 3b2d6e56..5d550cfc 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -58,7 +58,7 @@ SMAPI uses a small number of conditional compilation constants, which you can se flag | purpose ---- | ------- `SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`. -`HARMONY_2` | Whether to enable experimental Harmony 2.0 support. Existing Harmony 1._x_ mods will be rewritten automatically for compatibility. +`HARMONY_2` | Whether to enable experimental Harmony 2.0 support and rewrite existing Harmony 1._x_ mods for compatibility. Note that you need to replace `build/0Harmony.dll` with a Harmony 2.0 build (or switch to a package reference) to use this flag. ## For SMAPI developers ### Compiling from source @@ -102,14 +102,10 @@ on the wiki for the first-time setup. 3. Rename the folders to `SMAPI installer` and `SMAPI installer for developers`. 4. Zip the two folders. -### Using a custom Harmony build -The official SMAPI releases include [a custom build of Harmony](https://github.com/Pathoschild/Harmony), -but compiling from source will use the official build. To use a custom build, put `0Harmony.dll` in -the `build` folder and it'll be referenced automatically. - -Note that Harmony merges its dependencies into `0Harmony.dll` when compiled in release mode. To use -a debug build of Harmony, you'll need to manually copy those dependencies into your game's -`smapi-internal` folder. +### Custom Harmony build +SMAPI uses [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme), which is +included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that +folder. ## Release notes See [release notes](../release-notes.md). diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index c17de6d0..4af4527b 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -14,14 +14,13 @@ - - + -- cgit From b32cad4046916344665c67923482db09cacb366f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Jun 2020 11:13:23 -0400 Subject: add i18n schema to JSON validator --- docs/release-notes.md | 1 + docs/technical/web.md | 5 +++-- .../Controllers/JsonValidatorController.cs | 3 ++- src/SMAPI.Web/wwwroot/schemas/i18n.json | 24 ++++++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/SMAPI.Web/wwwroot/schemas/i18n.json (limited to 'docs/technical') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3c3f0796..43e1011c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -20,6 +20,7 @@ * For the web UI: * Added GitHub licenses to mod compatibility list. + * Added SMAPI `i18n` schema to JSON validator. * Updated ModDrop URLs. * Internal changes to improve performance and reliability. diff --git a/docs/technical/web.md b/docs/technical/web.md index d21b87ac..50237bfe 100644 --- a/docs/technical/web.md +++ b/docs/technical/web.md @@ -110,8 +110,9 @@ Available schemas: format | schema URL ------ | ---------- -[SMAPI `manifest.json`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest) | https://smapi.io/schemas/manifest.json -[Content Patcher `content.json`](https://github.com/Pathoschild/StardewMods/tree/develop/ContentPatcher#readme) | https://smapi.io/schemas/content-patcher.json +[SMAPI: `manifest.json`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest) | https://smapi.io/schemas/manifest.json +[SMAPI: translations (`i18n` folder)](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation) | https://smapi.io/schemas/i18n.json +[Content Patcher: `content.json`](https://github.com/Pathoschild/StardewMods/tree/develop/ContentPatcher#readme) | https://smapi.io/schemas/content-patcher.json ## Web API ### Overview diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index c43fb929..8fb9e06a 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -27,7 +27,8 @@ namespace StardewModdingAPI.Web.Controllers private readonly IDictionary SchemaFormats = new Dictionary { ["none"] = "None", - ["manifest"] = "Manifest", + ["manifest"] = "SMAPI: manifest", + ["i18n"] = "SMAPI: translations (i18n)", ["content-patcher"] = "Content Patcher" }; diff --git a/src/SMAPI.Web/wwwroot/schemas/i18n.json b/src/SMAPI.Web/wwwroot/schemas/i18n.json new file mode 100644 index 00000000..493ad213 --- /dev/null +++ b/src/SMAPI.Web/wwwroot/schemas/i18n.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://smapi.io/schemas/i18n.json", + "title": "SMAPI i18n file", + "description": "A translation file for a SMAPI mod or content pack.", + "@documentationUrl": "https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation", + "type": "object", + + "properties": { + "$schema": { + "title": "Schema", + "description": "A reference to this JSON schema. Not part of the actual format, but useful for validation tools.", + "type": "string", + "const": "https://smapi.io/schemas/manifest.json" + } + }, + + "additionalProperties": { + "type": "string", + "@errorMessages": { + "type": "Invalid property. Translation files can only contain text property values." + } + } +} -- cgit From cb9ff7019995eff92104703f097856d2523e02ce Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Jun 2020 12:42:31 -0400 Subject: prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 15 +++++++++------ docs/technical/smapi.md | 4 ++-- src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) (limited to 'docs/technical') diff --git a/build/common.targets b/build/common.targets index ddfbd71a..f66f51df 100644 --- a/build/common.targets +++ b/build/common.targets @@ -4,7 +4,7 @@ - 3.5.0 + 3.6.0 SMAPI latest diff --git a/docs/release-notes.md b/docs/release-notes.md index b25defa8..1c5dc1dd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,21 +2,23 @@ # Release notes -## Upcoming release +## 3.6 +Released 20 June 2020 for Stardew Valley 1.4.1 or later. + * For players: + * Added crossplatform compatibility for mods which use the `[HarmonyPatch(type)]` attribute. * Added experimental option to reduce startup time when loading mod DLLs (thanks to ZaneYork!). Enable `RewriteInParallel` in the `smapi-internal/config.json` to try it. * Reduced processing time when a mod loads many unpacked images (thanks to Entoarox!). - * Mod warnings are now listed alphabetically. + * Mod load warnings are now listed alphabetically. * MacOS files starting with `._` are now ignored and can no longer cause skipped mods. * Simplified paranoid warning logs and reduced their log level. - * Mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility. - * Fixed `BadImageFormatException` error detection. * Fixed black maps on Android for mods which use `.tmx` files. + * Fixed `BadImageFormatException` error detection. * Fixed `reload_i18n` command not reloading content pack translations. * For the web UI: @@ -35,7 +37,7 @@ * Added `harmony_summary` console command to list or search current Harmony patches. * Added `Multiplayer.PeerConnected` event. * Added support for overriding update keys from the wiki compatibility list. - * Improved mod rewriting for compatibility to support custom attributes and generic types. + * Improved mod rewriting for compatibility to support more cases (e.g. custom attributes and generic types). * Fixed `helper.Reflection` blocking access to game methods/properties intercepted by SMAPI. * Fixed asset propagation for Gil's portraits. * Fixed `.pdb` files ignored for error stack traces when mods are rewritten by SMAPI. @@ -45,6 +47,7 @@ * 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. * Overhauled update checks to simplify mod site integrations, centralize common logic, and enable upcoming features. * Merged the separate legacy redirects app on AWS into the main app on Azure. + * Changed SMAPI's Harmony ID from `io.smapi` to `SMAPI` for readability in Harmony summaries. ## 3.5 Released 27 April 2020 for Stardew Valley 1.4.1 or later. diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index 5d550cfc..3cb450ae 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -81,8 +81,8 @@ To prepare a crossplatform SMAPI release, you'll need to compile it on two platf [crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms) on the wiki for the first-time setup. -1. Update the version number in `.root/build/common.targets` and `Constants::Version`. Make sure - you use a [semantic version](https://semver.org). Recommended format: +1. Update the version numbers in `build/common.targets`, `Constants`, and the `manifest.json` for + bundled mods. Make sure you use a [semantic version](https://semver.org). Recommended format: build type | format | example :--------- | :----------------------- | :------ diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 908d4f65..05304312 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.5.0", + "Version": "3.6.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.5.0" + "MinimumApiVersion": "3.6.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index cd42459e..5d5336e7 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.5.0", + "Version": "3.6.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.5.0" + "MinimumApiVersion": "3.6.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 9d510d2d..66971709 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -20,7 +20,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.5.0"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.6.0"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.1"); -- cgit