diff options
Diffstat (limited to 'src/StardewModdingAPI.Toolkit')
40 files changed, 0 insertions, 2740 deletions
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs deleted file mode 100644 index 8a9c0a25..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi -{ - /// <summary>Metadata about a mod.</summary> - public class ModEntryModel - { - /********* - ** Accessors - *********/ - /// <summary>The mod's unique ID (if known).</summary> - public string ID { get; set; } - - /// <summary>The main version.</summary> - public ModEntryVersionModel Main { get; set; } - - /// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary> - public ModEntryVersionModel Optional { get; set; } - - /// <summary>The latest unofficial version, if newer than <see cref="Main"/> and <see cref="Optional"/>.</summary> - public ModEntryVersionModel Unofficial { get; set; } - - /// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see <see cref="HasBetaInfo"/>).</summary> - public ModEntryVersionModel UnofficialForBeta { get; set; } - - /// <summary>Optional extended data which isn't needed for update checks.</summary> - public ModExtendedMetadataModel Metadata { get; set; } - - /// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="UnofficialForBeta"/> should be used for beta versions of SMAPI instead of <see cref="Unofficial"/>.</summary> - public bool HasBetaInfo { get; set; } - - /// <summary>The errors that occurred while fetching update data.</summary> - public string[] Errors { get; set; } = new string[0]; - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs deleted file mode 100644 index dadb8c10..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi -{ - /// <summary>Metadata about a version.</summary> - public class ModEntryVersionModel - { - /********* - ** Accessors - *********/ - /// <summary>The version number.</summary> - public ISemanticVersion Version { get; set; } - - /// <summary>The mod page URL.</summary> - public string Url { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - public ModEntryVersionModel() { } - - /// <summary>Construct an instance.</summary> - /// <param name="version">The version number.</param> - /// <param name="url">The mod page URL.</param> - public ModEntryVersionModel(ISemanticVersion version, string url) - { - this.Version = version; - this.Url = url; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs deleted file mode 100644 index 989c18b0..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; -using StardewModdingAPI.Toolkit.Framework.ModData; - -namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi -{ - /// <summary>Extended metadata about a mod.</summary> - public class ModExtendedMetadataModel - { - /********* - ** Accessors - *********/ - /**** - ** Mod info - ****/ - /// <summary>The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates).</summary> - public string[] ID { get; set; } = new string[0]; - - /// <summary>The mod's display name.</summary> - public string Name { get; set; } - - /// <summary>The mod ID on Nexus.</summary> - public int? NexusID { get; set; } - - /// <summary>The mod ID in the Chucklefish mod repo.</summary> - public int? ChucklefishID { get; set; } - - /// <summary>The mod ID in the ModDrop mod repo.</summary> - public int? ModDropID { get; set; } - - /// <summary>The GitHub repository in the form 'owner/repo'.</summary> - public string GitHubRepo { get; set; } - - /// <summary>The URL to a non-GitHub source repo.</summary> - public string CustomSourceUrl { get; set; } - - /// <summary>The custom mod page URL (if applicable).</summary> - public string CustomUrl { get; set; } - - - /**** - ** Stable compatibility - ****/ - /// <summary>The compatibility status.</summary> - [JsonConverter(typeof(StringEnumConverter))] - public WikiCompatibilityStatus? CompatibilityStatus { get; set; } - - /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatitng.</summary> - public string CompatibilitySummary { get; set; } - - /// <summary>The game or SMAPI version which broke this mod, if applicable.</summary> - public string BrokeIn { get; set; } - - - /**** - ** Beta compatibility - ****/ - /// <summary>The compatibility status for the Stardew Valley beta (if any).</summary> - [JsonConverter(typeof(StringEnumConverter))] - public WikiCompatibilityStatus? BetaCompatibilityStatus { get; set; } - - /// <summary>The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatitng.</summary> - public string BetaCompatibilitySummary { get; set; } - - /// <summary>The beta game or SMAPI version which broke this mod, if applicable.</summary> - public string BetaBrokeIn { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - public ModExtendedMetadataModel() { } - - /// <summary>Construct an instance.</summary> - /// <param name="wiki">The mod metadata from the wiki (if available).</param> - /// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param> - public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db) - { - // wiki data - if (wiki != null) - { - this.ID = wiki.ID; - this.Name = wiki.Name.FirstOrDefault(); - this.NexusID = wiki.NexusID; - this.ChucklefishID = wiki.ChucklefishID; - this.ModDropID = wiki.ModDropID; - this.GitHubRepo = wiki.GitHubRepo; - this.CustomSourceUrl = wiki.CustomSourceUrl; - this.CustomUrl = wiki.CustomUrl; - - this.CompatibilityStatus = wiki.Compatibility.Status; - this.CompatibilitySummary = wiki.Compatibility.Summary; - this.BrokeIn = wiki.Compatibility.BrokeIn; - - this.BetaCompatibilityStatus = wiki.BetaCompatibility?.Status; - this.BetaCompatibilitySummary = wiki.BetaCompatibility?.Summary; - this.BetaBrokeIn = wiki.BetaCompatibility?.BrokeIn; - } - - // internal DB data - if (db != null) - { - this.ID = this.ID.Union(db.FormerIDs).ToArray(); - this.Name = this.Name ?? db.DisplayName; - } - } - - /// <summary>Get update keys based on the metadata.</summary> - public IEnumerable<string> GetUpdateKeys() - { - if (this.NexusID.HasValue) - yield return $"Nexus:{this.NexusID}"; - if (this.ChucklefishID.HasValue) - yield return $"Chucklefish:{this.ChucklefishID}"; - if (this.GitHubRepo != null) - yield return $"GitHub:{this.GitHubRepo}"; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs deleted file mode 100644 index e352e1cc..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; - -namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi -{ - /// <summary>Specifies mods whose update-check info to fetch.</summary> - public class ModSearchModel - { - /********* - ** Accessors - *********/ - /// <summary>The mods for which to find data.</summary> - public ModSearchEntryModel[] Mods { get; set; } - - /// <summary>Whether to include extended metadata for each mod.</summary> - public bool IncludeExtendedMetadata { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an empty instance.</summary> - public ModSearchModel() - { - // needed for JSON deserialising - } - - /// <summary>Construct an instance.</summary> - /// <param name="mods">The mods to search.</param> - /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> - public ModSearchModel(ModSearchEntryModel[] mods, bool includeExtendedMetadata) - { - this.Mods = mods.ToArray(); - this.IncludeExtendedMetadata = includeExtendedMetadata; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs deleted file mode 100644 index bca47647..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi -{ - /// <summary>Specifies the identifiers for a mod to match.</summary> - public class ModSearchEntryModel - { - /********* - ** Accessors - *********/ - /// <summary>The unique mod ID.</summary> - public string ID { get; set; } - - /// <summary>The namespaced mod update keys (if available).</summary> - public string[] UpdateKeys { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an empty instance.</summary> - public ModSearchEntryModel() - { - // needed for JSON deserialising - } - - /// <summary>Construct an instance.</summary> - /// <param name="id">The unique mod ID.</param> - /// <param name="updateKeys">The namespaced mod update keys (if available).</param> - public ModSearchEntryModel(string id, string[] updateKeys) - { - this.ID = id; - this.UpdateKeys = updateKeys ?? new string[0]; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs deleted file mode 100644 index 7c3df384..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using Newtonsoft.Json; -using StardewModdingAPI.Toolkit.Serialisation; - -namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi -{ - /// <summary>Provides methods for interacting with the SMAPI web API.</summary> - public class WebApiClient - { - /********* - ** Fields - *********/ - /// <summary>The base URL for the web API.</summary> - private readonly Uri BaseUrl; - - /// <summary>The API version number.</summary> - private readonly ISemanticVersion Version; - - /// <summary>The JSON serializer settings to use.</summary> - private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings; - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="baseUrl">The base URL for the web API.</param> - /// <param name="version">The web API version.</param> - public WebApiClient(string baseUrl, ISemanticVersion version) - { - this.BaseUrl = new Uri(baseUrl); - this.Version = version; - } - - /// <summary>Get metadata about a set of mods from the web API.</summary> - /// <param name="mods">The mod keys for which to fetch the latest version.</param> - /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> - public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, bool includeExtendedMetadata = false) - { - return this.Post<ModSearchModel, ModEntryModel[]>( - $"v{this.Version}/mods", - new ModSearchModel(mods, includeExtendedMetadata) - ).ToDictionary(p => p.ID); - } - - - /********* - ** Private methods - *********/ - /// <summary>Fetch the response from the backend API.</summary> - /// <typeparam name="TBody">The body content type.</typeparam> - /// <typeparam name="TResult">The expected response type.</typeparam> - /// <param name="url">The request URL, optionally excluding the base URL.</param> - /// <param name="content">The body content to post.</param> - private TResult Post<TBody, TResult>(string url, TBody content) - { - // note: avoid HttpClient for Mac compatibility - using (WebClient client = new WebClient()) - { - Uri fullUrl = new Uri(this.BaseUrl, url); - string data = JsonConvert.SerializeObject(content); - - client.Headers["Content-Type"] = "application/json"; - client.Headers["User-Agent"] = $"SMAPI/{this.Version}"; - string response = client.UploadString(fullUrl, data); - return JsonConvert.DeserializeObject<TResult>(response, this.JsonSettings); - } - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs deleted file mode 100644 index 3e9b8ea6..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using HtmlAgilityPack; -using Pathoschild.Http.Client; - -namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki -{ - /// <summary>An HTTP client for fetching mod metadata from the wiki.</summary> - public class WikiClient : IDisposable - { - /********* - ** Fields - *********/ - /// <summary>The underlying HTTP client.</summary> - private readonly IClient Client; - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="userAgent">The user agent for the wiki API.</param> - /// <param name="baseUrl">The base URL for the wiki API.</param> - public WikiClient(string userAgent, string baseUrl = "https://stardewvalleywiki.com/mediawiki/api.php") - { - this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent); - } - - /// <summary>Fetch mods from the compatibility list.</summary> - public async Task<WikiModList> FetchModsAsync() - { - // fetch HTML - ResponseModel response = await this.Client - .GetAsync("") - .WithArguments(new - { - action = "parse", - page = "Modding:Mod_compatibility", - format = "json" - }) - .As<ResponseModel>(); - string html = response.Parse.Text["*"]; - - // parse HTML - var doc = new HtmlDocument(); - doc.LoadHtml(html); - - // fetch game versions - string stableVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-stable-version']")?.InnerText; - string betaVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-beta-version']")?.InnerText; - if (betaVersion == stableVersion) - betaVersion = null; - - // find mod entries - HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']"); - if (modNodes == null) - throw new InvalidOperationException("Can't parse wiki compatibility list, no mods found."); - - // parse - WikiModEntry[] mods = this.ParseEntries(modNodes).ToArray(); - return new WikiModList - { - StableVersion = stableVersion, - BetaVersion = betaVersion, - Mods = mods - }; - } - - /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> - public void Dispose() - { - this.Client?.Dispose(); - } - - - /********* - ** Private methods - *********/ - /// <summary>Parse valid mod compatibility entries.</summary> - /// <param name="nodes">The HTML compatibility entries.</param> - private IEnumerable<WikiModEntry> ParseEntries(IEnumerable<HtmlNode> nodes) - { - foreach (HtmlNode node in nodes) - { - // extract fields - string[] names = this.GetAttributeAsCsv(node, "data-name"); - string[] authors = this.GetAttributeAsCsv(node, "data-author"); - string[] ids = this.GetAttributeAsCsv(node, "data-id"); - string[] warnings = this.GetAttributeAsCsv(node, "data-warnings"); - int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id"); - int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id"); - int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id"); - string githubRepo = this.GetAttribute(node, "data-github"); - string customSourceUrl = this.GetAttribute(node, "data-custom-source"); - string customUrl = this.GetAttribute(node, "data-url"); - string anchor = this.GetAttribute(node, "id"); - string contentPackFor = this.GetAttribute(node, "data-content-pack-for"); - - // parse stable compatibility - WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo - { - Status = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-status") ?? WikiCompatibilityStatus.Ok, - BrokeIn = this.GetAttribute(node, "data-broke-in"), - UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-unofficial-version"), - UnofficialUrl = this.GetAttribute(node, "data-unofficial-url"), - Summary = this.GetInnerHtml(node, "mod-summary")?.Trim() - }; - - // parse beta compatibility - WikiCompatibilityInfo betaCompatibility = null; - { - WikiCompatibilityStatus? betaStatus = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-beta-status"); - if (betaStatus.HasValue) - { - betaCompatibility = new WikiCompatibilityInfo - { - Status = betaStatus.Value, - BrokeIn = this.GetAttribute(node, "data-beta-broke-in"), - UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-beta-unofficial-version"), - UnofficialUrl = this.GetAttribute(node, "data-beta-unofficial-url"), - Summary = this.GetInnerHtml(node, "mod-beta-summary") - }; - } - } - - // yield model - yield return new WikiModEntry - { - ID = ids, - Name = names, - Author = authors, - NexusID = nexusID, - ChucklefishID = chucklefishID, - ModDropID = modDropID, - GitHubRepo = githubRepo, - CustomSourceUrl = customSourceUrl, - CustomUrl = customUrl, - ContentPackFor = contentPackFor, - Compatibility = compatibility, - BetaCompatibility = betaCompatibility, - Warnings = warnings, - Anchor = anchor - }; - } - } - - /// <summary>Get an attribute value.</summary> - /// <param name="element">The element whose attributes to read.</param> - /// <param name="name">The attribute name.</param> - private string GetAttribute(HtmlNode element, string name) - { - string value = element.GetAttributeValue(name, null); - if (string.IsNullOrWhiteSpace(value)) - return null; - - return WebUtility.HtmlDecode(value); - } - - /// <summary>Get an attribute value and parse it as a comma-delimited list of strings.</summary> - /// <param name="element">The element whose attributes to read.</param> - /// <param name="name">The attribute name.</param> - private string[] GetAttributeAsCsv(HtmlNode element, string name) - { - string raw = this.GetAttribute(element, name); - return !string.IsNullOrWhiteSpace(raw) - ? raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray() - : new string[0]; - } - - /// <summary>Get an attribute value and parse it as an enum value.</summary> - /// <typeparam name="TEnum">The enum type.</typeparam> - /// <param name="element">The element whose attributes to read.</param> - /// <param name="name">The attribute name.</param> - private TEnum? GetAttributeAsEnum<TEnum>(HtmlNode element, string name) where TEnum : struct - { - string raw = this.GetAttribute(element, name); - if (raw == null) - return null; - if (!Enum.TryParse(raw, true, out TEnum value) && Enum.IsDefined(typeof(TEnum), value)) - throw new InvalidOperationException($"Unknown {typeof(TEnum).Name} value '{raw}' when parsing compatibility list."); - return value; - } - - /// <summary>Get an attribute value and parse it as a semantic version.</summary> - /// <param name="element">The element whose attributes to read.</param> - /// <param name="name">The attribute name.</param> - private ISemanticVersion GetAttributeAsSemanticVersion(HtmlNode element, string name) - { - string raw = this.GetAttribute(element, name); - return SemanticVersion.TryParse(raw, out ISemanticVersion version) - ? version - : null; - } - - /// <summary>Get an attribute value and parse it as a nullable int.</summary> - /// <param name="element">The element whose attributes to read.</param> - /// <param name="name">The attribute name.</param> - private int? GetAttributeAsNullableInt(HtmlNode element, string name) - { - string raw = this.GetAttribute(element, name); - if (raw != null && int.TryParse(raw, out int value)) - return value; - return null; - } - - /// <summary>Get the text of an element with the given class name.</summary> - /// <param name="container">The metadata container.</param> - /// <param name="className">The field name.</param> - private string GetInnerHtml(HtmlNode container, string className) - { - return container.Descendants().FirstOrDefault(p => p.HasClass(className))?.InnerHtml; - } - - /// <summary>The response model for the MediaWiki parse API.</summary> - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] - private class ResponseModel - { - /// <summary>The parse API results.</summary> - public ResponseParseModel Parse { get; set; } - } - - /// <summary>The inner response model for the MediaWiki parse API.</summary> - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] - private class ResponseParseModel - { - /// <summary>The parsed text.</summary> - public IDictionary<string, string> Text { get; set; } - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs deleted file mode 100644 index 204acd2b..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki -{ - /// <summary>Compatibility info for a mod.</summary> - public class WikiCompatibilityInfo - { - /********* - ** Accessors - *********/ - /// <summary>The compatibility status.</summary> - public WikiCompatibilityStatus Status { get; set; } - - /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary> - public string Summary { get; set; } - - /// <summary>The game or SMAPI version which broke this mod (if applicable).</summary> - public string BrokeIn { get; set; } - - /// <summary>The version of the latest unofficial update, if applicable.</summary> - public ISemanticVersion UnofficialVersion { get; set; } - - /// <summary>The URL to the latest unofficial update, if applicable.</summary> - public string UnofficialUrl { get; set; } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs deleted file mode 100644 index a1d2dfae..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki -{ - /// <summary>The compatibility status for a mod.</summary> - public enum WikiCompatibilityStatus - { - /// <summary>The mod is compatible.</summary> - Ok = 0, - - /// <summary>The mod is compatible if you use an optional official download.</summary> - Optional = 1, - - /// <summary>The mod is compatible if you use an unofficial update.</summary> - Unofficial = 2, - - /// <summary>The mod isn't compatible, but the player can fix it or there's a good alternative.</summary> - Workaround = 3, - - /// <summary>The mod isn't compatible.</summary> - Broken = 4, - - /// <summary>The mod is no longer maintained by the author, and an unofficial update or continuation is unlikely.</summary> - Abandoned = 5, - - /// <summary>The mod is no longer needed and should be removed.</summary> - Obsolete = 6 - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs deleted file mode 100644 index cf416cc6..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki -{ - /// <summary>A mod entry in the wiki list.</summary> - public class WikiModEntry - { - /********* - ** Accessors - *********/ - /// <summary>The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order.</summary> - public string[] ID { get; set; } - - /// <summary>The mod's display name. If the mod has multiple names, the first one is the most canonical name.</summary> - public string[] Name { get; set; } - - /// <summary>The mod's author name. If the author has multiple names, the first one is the most canonical name.</summary> - public string[] Author { get; set; } - - /// <summary>The mod ID on Nexus.</summary> - public int? NexusID { get; set; } - - /// <summary>The mod ID in the Chucklefish mod repo.</summary> - public int? ChucklefishID { get; set; } - - /// <summary>The mod ID in the ModDrop mod repo.</summary> - public int? ModDropID { get; set; } - - /// <summary>The GitHub repository in the form 'owner/repo'.</summary> - public string GitHubRepo { get; set; } - - /// <summary>The URL to a non-GitHub source repo.</summary> - public string CustomSourceUrl { get; set; } - - /// <summary>The custom mod page URL (if applicable).</summary> - public string CustomUrl { get; set; } - - /// <summary>The name of the mod which loads this content pack, if applicable.</summary> - public string ContentPackFor { get; set; } - - /// <summary>The mod's compatibility with the latest stable version of the game.</summary> - public WikiCompatibilityInfo Compatibility { get; set; } - - /// <summary>The mod's compatibility with the latest beta version of the game (if any).</summary> - public WikiCompatibilityInfo BetaCompatibility { get; set; } - - /// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="BetaCompatibility"/> should be used for beta versions of SMAPI instead of <see cref="Compatibility"/>.</summary> - public bool HasBetaInfo => this.BetaCompatibility != null; - - /// <summary>The human-readable warnings for players about this mod.</summary> - public string[] Warnings { get; set; } - - /// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary> - public string Anchor { get; set; } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs deleted file mode 100644 index 0d614f28..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki -{ - /// <summary>Metadata from the wiki's mod compatibility list.</summary> - public class WikiModList - { - /********* - ** Accessors - *********/ - /// <summary>The stable game version.</summary> - public string StableVersion { get; set; } - - /// <summary>The beta game version (if any).</summary> - public string BetaVersion { get; set; } - - /// <summary>The mods on the wiki.</summary> - public WikiModEntry[] Mods { get; set; } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs deleted file mode 100644 index ef6d4dd9..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>The SMAPI predefined metadata.</summary> - internal class MetadataModel - { - /******** - ** Accessors - ********/ - /// <summary>Extra metadata about mods.</summary> - public IDictionary<string, ModDataModel> ModData { get; set; } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs deleted file mode 100644 index b3954693..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Linq; - -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>A versioned mod metadata field.</summary> - public class ModDataField - { - /********* - ** Accessors - *********/ - /// <summary>The field key.</summary> - public ModDataFieldKey Key { get; } - - /// <summary>The field value.</summary> - public string Value { get; } - - /// <summary>Whether this field should only be applied if it's not already set.</summary> - public bool IsDefault { get; } - - /// <summary>The lowest version in the range, or <c>null</c> for all past versions.</summary> - public ISemanticVersion LowerVersion { get; } - - /// <summary>The highest version in the range, or <c>null</c> for all future versions.</summary> - public ISemanticVersion UpperVersion { get; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="key">The field key.</param> - /// <param name="value">The field value.</param> - /// <param name="isDefault">Whether this field should only be applied if it's not already set.</param> - /// <param name="lowerVersion">The lowest version in the range, or <c>null</c> for all past versions.</param> - /// <param name="upperVersion">The highest version in the range, or <c>null</c> for all future versions.</param> - public ModDataField(ModDataFieldKey key, string value, bool isDefault, ISemanticVersion lowerVersion, ISemanticVersion upperVersion) - { - this.Key = key; - this.Value = value; - this.IsDefault = isDefault; - this.LowerVersion = lowerVersion; - this.UpperVersion = upperVersion; - } - - /// <summary>Get whether this data field applies for the given manifest.</summary> - /// <param name="manifest">The mod manifest.</param> - public bool IsMatch(IManifest manifest) - { - return - manifest?.Version != null // ignore invalid manifest - && (!this.IsDefault || !this.HasFieldValue(manifest, this.Key)) - && (this.LowerVersion == null || !manifest.Version.IsOlderThan(this.LowerVersion)) - && (this.UpperVersion == null || !manifest.Version.IsNewerThan(this.UpperVersion)); - } - - - /********* - ** Private methods - *********/ - /// <summary>Get whether a manifest field has a meaningful value for the purposes of enforcing <see cref="IsDefault"/>.</summary> - /// <param name="manifest">The mod manifest.</param> - /// <param name="key">The field key matching <see cref="ModDataFieldKey"/>.</param> - private bool HasFieldValue(IManifest manifest, ModDataFieldKey key) - { - switch (key) - { - // update key - case ModDataFieldKey.UpdateKey: - return manifest.UpdateKeys != null && manifest.UpdateKeys.Any(p => !string.IsNullOrWhiteSpace(p)); - - // non-manifest fields - case ModDataFieldKey.AlternativeUrl: - case ModDataFieldKey.StatusReasonPhrase: - case ModDataFieldKey.Status: - return false; - - default: - return false; - } - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs deleted file mode 100644 index 09dd0cc5..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>The valid field keys.</summary> - public enum ModDataFieldKey - { - /// <summary>A manifest update key.</summary> - UpdateKey, - - /// <summary>An alternative URL the player can check for an updated version.</summary> - AlternativeUrl, - - /// <summary>The mod's predefined compatibility status.</summary> - Status, - - /// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary> - StatusReasonPhrase - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs deleted file mode 100644 index 18039762..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>The raw mod metadata from SMAPI's internal mod list.</summary> - internal class ModDataModel - { - /********* - ** Accessors - *********/ - /// <summary>The mod's current unique ID.</summary> - public string ID { get; set; } - - /// <summary>The former mod IDs (if any).</summary> - /// <remarks> - /// This uses a custom format which uniquely identifies a mod across multiple versions and - /// supports matching other fields if no ID was specified. This doesn't include the latest - /// ID, if any. If the mod's ID changed over time, multiple variants can be separated by the - /// <c>|</c> character. - /// </remarks> - public string FormerIDs { get; set; } - - /// <summary>Maps local versions to a semantic version for update checks.</summary> - public IDictionary<string, string> MapLocalVersions { get; set; } = new Dictionary<string, string>(); - - /// <summary>Maps remote versions to a semantic version for update checks.</summary> - public IDictionary<string, string> MapRemoteVersions { get; set; } = new Dictionary<string, string>(); - - /// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary> - public ModWarning SuppressWarnings { get; set; } - - /// <summary>This field stores properties that aren't mapped to another field before they're parsed into <see cref="Fields"/>.</summary> - [JsonExtensionData] - public IDictionary<string, JToken> ExtensionData { get; set; } - - /// <summary>The versioned field data.</summary> - /// <remarks> - /// This maps field names to values. This should be accessed via <see cref="GetFields"/>. - /// Format notes: - /// - Each key consists of a field name prefixed with any combination of version range - /// and <c>Default</c>, separated by pipes (whitespace trimmed). For example, <c>Name</c> - /// will always override the name, <c>Default | Name</c> will only override a blank - /// name, and <c>~1.1 | Default | Name</c> will override blank names up to version 1.1. - /// - The version format is <c>min~max</c> (where either side can be blank for unbounded), or - /// a single version number. - /// - The field name itself corresponds to a <see cref="ModDataFieldKey"/> value. - /// </remarks> - public IDictionary<string, string> Fields { get; set; } = new Dictionary<string, string>(); - - - /********* - ** Public methods - *********/ - /// <summary>Get a parsed representation of the <see cref="Fields"/>.</summary> - public IEnumerable<ModDataField> GetFields() - { - foreach (KeyValuePair<string, string> pair in this.Fields) - { - // init fields - string packedKey = pair.Key; - string value = pair.Value; - bool isDefault = false; - ISemanticVersion lowerVersion = null; - ISemanticVersion upperVersion = null; - - // parse - string[] parts = packedKey.Split('|').Select(p => p.Trim()).ToArray(); - ModDataFieldKey fieldKey = (ModDataFieldKey)Enum.Parse(typeof(ModDataFieldKey), parts.Last(), ignoreCase: true); - foreach (string part in parts.Take(parts.Length - 1)) - { - // 'default' - if (part.Equals("Default", StringComparison.InvariantCultureIgnoreCase)) - { - isDefault = true; - continue; - } - - // version range - if (part.Contains("~")) - { - string[] versionParts = part.Split(new[] { '~' }, 2); - lowerVersion = versionParts[0] != "" ? new SemanticVersion(versionParts[0]) : null; - upperVersion = versionParts[1] != "" ? new SemanticVersion(versionParts[1]) : null; - continue; - } - - // single version - lowerVersion = new SemanticVersion(part); - upperVersion = new SemanticVersion(part); - } - - yield return new ModDataField(fieldKey, value, isDefault, lowerVersion, upperVersion); - } - } - - /// <summary>Get the former mod IDs.</summary> - public IEnumerable<string> GetFormerIDs() - { - if (this.FormerIDs != null) - { - foreach (string id in this.FormerIDs.Split('|')) - yield return id.Trim(); - } - } - - - /********* - ** Private methods - *********/ - /// <summary>The method invoked after JSON deserialisation.</summary> - /// <param name="context">The deserialisation context.</param> - [OnDeserialized] - private void OnDeserialized(StreamingContext context) - { - if (this.ExtensionData != null) - { - this.Fields = this.ExtensionData.ToDictionary(p => p.Key, p => p.Value.ToString()); - this.ExtensionData = null; - } - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs deleted file mode 100644 index 794ad2e4..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>The parsed mod metadata from SMAPI's internal mod list.</summary> - public class ModDataRecord - { - /********* - ** Accessors - *********/ - /// <summary>The mod's default display name.</summary> - public string DisplayName { get; } - - /// <summary>The mod's current unique ID.</summary> - public string ID { get; } - - /// <summary>The former mod IDs (if any).</summary> - public string[] FormerIDs { get; } - - /// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary> - public ModWarning SuppressWarnings { get; set; } - - /// <summary>Maps local versions to a semantic version for update checks.</summary> - public IDictionary<string, string> MapLocalVersions { get; } - - /// <summary>Maps remote versions to a semantic version for update checks.</summary> - public IDictionary<string, string> MapRemoteVersions { get; } - - /// <summary>The versioned field data.</summary> - public ModDataField[] Fields { get; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="displayName">The mod's default display name.</param> - /// <param name="model">The raw data model.</param> - internal ModDataRecord(string displayName, ModDataModel model) - { - this.DisplayName = displayName; - this.ID = model.ID; - this.FormerIDs = model.GetFormerIDs().ToArray(); - this.SuppressWarnings = model.SuppressWarnings; - this.MapLocalVersions = new Dictionary<string, string>(model.MapLocalVersions, StringComparer.InvariantCultureIgnoreCase); - this.MapRemoteVersions = new Dictionary<string, string>(model.MapRemoteVersions, StringComparer.InvariantCultureIgnoreCase); - this.Fields = model.GetFields().ToArray(); - } - - /// <summary>Get whether the mod has (or previously had) the given ID.</summary> - /// <param name="id">The mod ID.</param> - public bool HasID(string id) - { - // try main ID - if (this.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) - return true; - - // try former IDs - foreach (string formerID in this.FormerIDs) - { - if (formerID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) - return true; - } - - return false; - } - - /// <summary>Get a semantic local version for update checks.</summary> - /// <param name="version">The remote version to normalise.</param> - public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version) - { - return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion) - ? new SemanticVersion(newVersion) - : version; - } - - /// <summary>Get a semantic remote version for update checks.</summary> - /// <param name="version">The remote version to normalise.</param> - public string GetRemoteVersionForUpdateChecks(string version) - { - // normalise version if possible - if (SemanticVersion.TryParse(version, out ISemanticVersion parsed)) - version = parsed.ToString(); - - // fetch remote version - return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion) - ? newVersion - : version; - } - - /// <summary>Get the possible mod IDs.</summary> - public IEnumerable<string> GetIDs() - { - return this.FormerIDs - .Concat(new[] { this.ID }) - .Where(p => !string.IsNullOrWhiteSpace(p)) - .Select(p => p.Trim()) - .Distinct(); - } - - /// <summary>Get the default update key for this mod, if any.</summary> - public string GetDefaultUpdateKey() - { - string updateKey = this.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value; - return !string.IsNullOrWhiteSpace(updateKey) - ? updateKey - : null; - } - - /// <summary>Get a parsed representation of the <see cref="ModDataRecord.Fields"/> which match a given manifest.</summary> - /// <param name="manifest">The manifest to match.</param> - public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest) - { - ModDataRecordVersionedFields parsed = new ModDataRecordVersionedFields { DisplayName = this.DisplayName, DataRecord = this }; - foreach (ModDataField field in this.Fields.Where(field => field.IsMatch(manifest))) - { - switch (field.Key) - { - // update key - case ModDataFieldKey.UpdateKey: - parsed.UpdateKey = field.Value; - break; - - // alternative URL - case ModDataFieldKey.AlternativeUrl: - parsed.AlternativeUrl = field.Value; - break; - - // status - case ModDataFieldKey.Status: - parsed.Status = (ModStatus)Enum.Parse(typeof(ModStatus), field.Value, ignoreCase: true); - parsed.StatusUpperVersion = field.UpperVersion; - break; - - // status reason phrase - case ModDataFieldKey.StatusReasonPhrase: - parsed.StatusReasonPhrase = field.Value; - break; - } - } - - return parsed; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs deleted file mode 100644 index 237f2c66..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>The versioned fields from a <see cref="ModDataRecord"/> for a specific manifest.</summary> - public class ModDataRecordVersionedFields - { - /********* - ** Accessors - *********/ - /// <summary>The underlying data record.</summary> - public ModDataRecord DataRecord { get; set; } - - /// <summary>The default mod name to display when the name isn't available (e.g. during dependency checks).</summary> - public string DisplayName { get; set; } - - /// <summary>The update key to apply.</summary> - public string UpdateKey { get; set; } - - /// <summary>The alternative URL the player can check for an updated version.</summary> - public string AlternativeUrl { get; set; } - - /// <summary>The predefined compatibility status.</summary> - public ModStatus Status { get; set; } = ModStatus.None; - - /// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary> - public string StatusReasonPhrase { get; set; } - - /// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary> - public ISemanticVersion StatusUpperVersion { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Get a semantic local version for update checks.</summary> - /// <param name="version">The remote version to normalise.</param> - public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version) - { - return this.DataRecord.GetLocalVersionForUpdateChecks(version); - } - - /// <summary>Get a semantic remote version for update checks.</summary> - /// <param name="version">The remote version to normalise.</param> - public ISemanticVersion GetRemoteVersionForUpdateChecks(ISemanticVersion version) - { - if (version == null) - return null; - - string rawVersion = this.DataRecord.GetRemoteVersionForUpdateChecks(version.ToString()); - return rawVersion != null - ? new SemanticVersion(rawVersion) - : version; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs deleted file mode 100644 index a9da884a..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>Handles access to SMAPI's internal mod metadata list.</summary> - public class ModDatabase - { - /********* - ** Fields - *********/ - /// <summary>The underlying mod data records indexed by default display name.</summary> - private readonly ModDataRecord[] Records; - - /// <summary>Get an update URL for an update key (if valid).</summary> - private readonly Func<string, string> GetUpdateUrl; - - - /********* - ** Public methods - *********/ - /// <summary>Construct an empty instance.</summary> - public ModDatabase() - : this(new ModDataRecord[0], key => null) { } - - /// <summary>Construct an instance.</summary> - /// <param name="records">The underlying mod data records indexed by default display name.</param> - /// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param> - public ModDatabase(IEnumerable<ModDataRecord> records, Func<string, string> getUpdateUrl) - { - this.Records = records.ToArray(); - this.GetUpdateUrl = getUpdateUrl; - } - - /// <summary>Get all mod data records.</summary> - public IEnumerable<ModDataRecord> GetAll() - { - return this.Records; - } - - /// <summary>Get a mod data record.</summary> - /// <param name="modID">The unique mod ID.</param> - public ModDataRecord Get(string modID) - { - return !string.IsNullOrWhiteSpace(modID) - ? this.Records.FirstOrDefault(p => p.HasID(modID)) - : null; - } - - /// <summary>Get the mod page URL for a mod (if available).</summary> - /// <param name="id">The unique mod ID.</param> - public string GetModPageUrlFor(string id) - { - // get update key - ModDataRecord record = this.Get(id); - ModDataField updateKeyField = record?.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey); - if (updateKeyField == null) - return null; - - // get update URL - return this.GetUpdateUrl(updateKeyField.Value); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs deleted file mode 100644 index 09da74bf..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>Indicates how SMAPI should treat a mod.</summary> - public enum ModStatus - { - /// <summary>Don't override the status.</summary> - None, - - /// <summary>The mod is obsolete and shouldn't be used, regardless of version.</summary> - Obsolete, - - /// <summary>Assume the mod is not compatible, even if SMAPI doesn't detect any incompatible code.</summary> - AssumeBroken, - - /// <summary>Assume the mod is compatible, even if SMAPI detects incompatible code.</summary> - AssumeCompatible - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModWarning.cs deleted file mode 100644 index d61c427f..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModWarning.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace StardewModdingAPI.Toolkit.Framework.ModData -{ - /// <summary>Indicates a detected non-error mod issue.</summary> - [Flags] - public enum ModWarning - { - /// <summary>No issues detected.</summary> - None = 0, - - /// <summary>SMAPI detected incompatible code in the mod, but was configured to load it anyway.</summary> - BrokenCodeLoaded = 1, - - /// <summary>The mod affects the save serializer in a way that may make saves unloadable without the mod.</summary> - ChangesSaveSerialiser = 2, - - /// <summary>The mod patches the game in a way that may impact stability.</summary> - PatchesGame = 4, - - /// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary> - UsesDynamic = 8, - - /// <summary>The mod references specialised 'unvalided update tick' events which may impact stability.</summary> - UsesUnvalidatedUpdateTick = 16, - - /// <summary>The mod has no update keys set.</summary> - NoUpdateKeys = 32, - - /// <summary>Uses .NET APIs for filesystem access.</summary> - AccessesFilesystem = 64, - - /// <summary>Uses .NET APIs for shell or process access.</summary> - AccessesShell = 128 - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs deleted file mode 100644 index bb467b36..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using StardewModdingAPI.Toolkit.Serialisation.Models; -using StardewModdingAPI.Toolkit.Utilities; - -namespace StardewModdingAPI.Toolkit.Framework.ModScanning -{ - /// <summary>The info about a mod read from its folder.</summary> - public class ModFolder - { - /********* - ** Accessors - *********/ - /// <summary>A suggested display name for the mod folder.</summary> - public string DisplayName { get; } - - /// <summary>The folder containing the mod's manifest.json.</summary> - public DirectoryInfo Directory { get; } - - /// <summary>The mod manifest.</summary> - public Manifest Manifest { get; } - - /// <summary>The error which occurred parsing the manifest, if any.</summary> - public string ManifestParseError { get; } - - /// <summary>Whether the mod should be loaded by default. This is <c>false</c> if it was found within a folder whose name starts with a dot.</summary> - public bool ShouldBeLoaded { get; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="root">The root folder containing mods.</param> - /// <param name="directory">The folder containing the mod's manifest.json.</param> - /// <param name="manifest">The mod manifest.</param> - /// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param> - /// <param name="shouldBeLoaded">Whether the mod should be loaded by default. This should be <c>false</c> if it was found within a folder whose name starts with a dot.</param> - public ModFolder(DirectoryInfo root, DirectoryInfo directory, Manifest manifest, string manifestParseError = null, bool shouldBeLoaded = true) - { - // save info - this.Directory = directory; - this.Manifest = manifest; - this.ManifestParseError = manifestParseError; - this.ShouldBeLoaded = shouldBeLoaded; - - // set display name - this.DisplayName = manifest?.Name; - if (string.IsNullOrWhiteSpace(this.DisplayName)) - this.DisplayName = PathUtilities.GetRelativePath(root.FullName, directory.FullName); - } - - /// <summary>Get the update keys for a mod.</summary> - /// <param name="manifest">The mod manifest.</param> - public IEnumerable<string> GetUpdateKeys(Manifest manifest) - { - return - (manifest.UpdateKeys ?? new string[0]) - .Where(p => !string.IsNullOrWhiteSpace(p)) - .ToArray(); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs deleted file mode 100644 index 0ab73d56..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using StardewModdingAPI.Toolkit.Serialisation; -using StardewModdingAPI.Toolkit.Serialisation.Models; - -namespace StardewModdingAPI.Toolkit.Framework.ModScanning -{ - /// <summary>Scans folders for mod data.</summary> - public class ModScanner - { - /********* - ** Fields - *********/ - /// <summary>The JSON helper with which to read manifests.</summary> - private readonly JsonHelper JsonHelper; - - /// <summary>A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod.</summary> - private readonly HashSet<string> IgnoreFilesystemEntries = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) - { - ".DS_Store", - "mcs", - "Thumbs.db" - }; - - /// <summary>The extensions for files which an XNB mod may contain. If a mod contains *only* these file extensions, it should be considered an XNB mod.</summary> - private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) - { - ".md", - ".png", - ".txt", - ".xnb" - }; - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="jsonHelper">The JSON helper with which to read manifests.</param> - public ModScanner(JsonHelper jsonHelper) - { - this.JsonHelper = jsonHelper; - } - - /// <summary>Extract information about all mods in the given folder.</summary> - /// <param name="rootPath">The root folder containing mods.</param> - public IEnumerable<ModFolder> GetModFolders(string rootPath) - { - DirectoryInfo root = new DirectoryInfo(rootPath); - return this.GetModFolders(root, root); - } - - /// <summary>Extract information from a mod folder.</summary> - /// <param name="root">The root folder containing mods.</param> - /// <param name="searchFolder">The folder to search for a mod.</param> - public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder) - { - // find manifest.json - FileInfo manifestFile = this.FindManifest(searchFolder); - - // set appropriate invalid-mod error - if (manifestFile == null) - { - FileInfo[] files = searchFolder.GetFiles("*", SearchOption.AllDirectories).Where(this.IsRelevant).ToArray(); - if (!files.Any()) - return new ModFolder(root, searchFolder, null, "it's an empty folder."); - if (files.All(file => this.PotentialXnbModExtensions.Contains(file.Extension))) - return new ModFolder(root, searchFolder, null, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); - return new ModFolder(root, searchFolder, null, "it contains files, but none of them are manifest.json."); - } - - // read mod info - Manifest manifest = null; - string manifestError = null; - { - try - { - if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest) || manifest == null) - manifestError = "its manifest is invalid."; - } - catch (SParseException ex) - { - manifestError = $"parsing its manifest failed: {ex.Message}"; - } - catch (Exception ex) - { - manifestError = $"parsing its manifest failed:\n{ex}"; - } - } - - // normalise display fields - if (manifest != null) - { - manifest.Name = this.StripNewlines(manifest.Name); - manifest.Description = this.StripNewlines(manifest.Description); - manifest.Author = this.StripNewlines(manifest.Author); - } - - return new ModFolder(root, manifestFile.Directory, manifest, manifestError); - } - - - /********* - ** Private methods - *********/ - /// <summary>Recursively extract information about all mods in the given folder.</summary> - /// <param name="root">The root mod folder.</param> - /// <param name="folder">The folder to search for mods.</param> - public IEnumerable<ModFolder> GetModFolders(DirectoryInfo root, DirectoryInfo folder) - { - // skip - if (folder.FullName != root.FullName && folder.Name.StartsWith(".")) - yield return new ModFolder(root, folder, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false); - - // recurse into subfolders - else if (this.IsModSearchFolder(root, folder)) - { - foreach (DirectoryInfo subfolder in folder.EnumerateDirectories()) - { - foreach (ModFolder match in this.GetModFolders(root, subfolder)) - yield return match; - } - } - - // treat as mod folder - else - yield return this.ReadFolder(root, folder); - } - - /// <summary>Find the manifest for a mod folder.</summary> - /// <param name="folder">The folder to search.</param> - private FileInfo FindManifest(DirectoryInfo folder) - { - while (true) - { - // check for manifest in current folder - FileInfo file = new FileInfo(Path.Combine(folder.FullName, "manifest.json")); - if (file.Exists) - return file; - - // check for single subfolder - FileSystemInfo[] entries = folder.EnumerateFileSystemInfos().Take(2).ToArray(); - if (entries.Length == 1 && entries[0] is DirectoryInfo subfolder) - { - folder = subfolder; - continue; - } - - // not found - return null; - } - } - - /// <summary>Get whether a given folder should be treated as a search folder (i.e. look for subfolders containing mods).</summary> - /// <param name="root">The root mod folder.</param> - /// <param name="folder">The folder to search for mods.</param> - private bool IsModSearchFolder(DirectoryInfo root, DirectoryInfo folder) - { - if (root.FullName == folder.FullName) - return true; - - DirectoryInfo[] subfolders = folder.GetDirectories().Where(this.IsRelevant).ToArray(); - FileInfo[] files = folder.GetFiles().Where(this.IsRelevant).ToArray(); - return subfolders.Any() && !files.Any(); - } - - /// <summary>Get whether a file or folder is relevant when deciding how to process a mod folder.</summary> - /// <param name="entry">The file or folder.</param> - private bool IsRelevant(FileSystemInfo entry) - { - return !this.IgnoreFilesystemEntries.Contains(entry.Name); - } - - /// <summary>Strip newlines from a string.</summary> - /// <param name="input">The input to strip.</param> - private string StripNewlines(string input) - { - return input?.Replace("\r", "").Replace("\n", ""); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs deleted file mode 100644 index f6c402d5..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.UpdateData -{ - /// <summary>A mod repository which SMAPI can check for updates.</summary> - public enum ModRepositoryKey - { - /// <summary>An unknown or invalid mod repository.</summary> - Unknown, - - /// <summary>The Chucklefish mod repository.</summary> - Chucklefish, - - /// <summary>A GitHub project containing releases.</summary> - GitHub, - - /// <summary>The ModDrop mod repository.</summary> - ModDrop, - - /// <summary>The Nexus Mods mod repository.</summary> - Nexus - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs deleted file mode 100644 index 865ebcf7..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; - -namespace StardewModdingAPI.Toolkit.Framework.UpdateData -{ - /// <summary>A namespaced mod ID which uniquely identifies a mod within a mod repository.</summary> - public class UpdateKey - { - /********* - ** Accessors - *********/ - /// <summary>The raw update key text.</summary> - public string RawText { get; } - - /// <summary>The mod repository containing the mod.</summary> - public ModRepositoryKey Repository { get; } - - /// <summary>The mod ID within the repository.</summary> - public string ID { get; } - - /// <summary>Whether the update key seems to be valid.</summary> - public bool LooksValid { get; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="rawText">The raw update key text.</param> - /// <param name="repository">The mod repository containing the mod.</param> - /// <param name="id">The mod ID within the repository.</param> - public UpdateKey(string rawText, ModRepositoryKey repository, string id) - { - this.RawText = rawText; - this.Repository = repository; - this.ID = id; - this.LooksValid = - repository != ModRepositoryKey.Unknown - && !string.IsNullOrWhiteSpace(id); - } - - /// <summary>Parse a raw update key.</summary> - /// <param name="raw">The raw update key to parse.</param> - public static UpdateKey Parse(string raw) - { - // split parts - string[] parts = raw?.Split(':'); - if (parts == null || parts.Length != 2) - return new UpdateKey(raw, ModRepositoryKey.Unknown, null); - - // extract parts - string repositoryKey = parts[0].Trim(); - string id = parts[1].Trim(); - if (string.IsNullOrWhiteSpace(id)) - id = null; - - // parse - if (!Enum.TryParse(repositoryKey, true, out ModRepositoryKey repository)) - return new UpdateKey(raw, ModRepositoryKey.Unknown, id); - if (id == null) - return new UpdateKey(raw, repository, null); - - return new UpdateKey(raw, repository, id); - } - - /// <summary>Get a string that represents the current object.</summary> - public override string ToString() - { - return this.LooksValid - ? $"{this.Repository}:{this.ID}" - : this.RawText; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/ModToolkit.cs b/src/StardewModdingAPI.Toolkit/ModToolkit.cs deleted file mode 100644 index 1b53e59e..00000000 --- a/src/StardewModdingAPI.Toolkit/ModToolkit.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; -using StardewModdingAPI.Toolkit.Framework.ModData; -using StardewModdingAPI.Toolkit.Framework.ModScanning; -using StardewModdingAPI.Toolkit.Serialisation; - -namespace StardewModdingAPI.Toolkit -{ - /// <summary>A convenience wrapper for the various tools.</summary> - public class ModToolkit - { - /********* - ** Fields - *********/ - /// <summary>The default HTTP user agent for the toolkit.</summary> - private readonly string UserAgent; - - /// <summary>Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID). This doesn't affect update checks, which defer to the remote web API.</summary> - private readonly IDictionary<string, string> VendorModUrls = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) - { - ["Chucklefish"] = "https://community.playstarbound.com/resources/{0}", - ["GitHub"] = "https://github.com/{0}/releases", - ["Nexus"] = "https://www.nexusmods.com/stardewvalley/mods/{0}" - }; - - - /********* - ** Accessors - *********/ - /// <summary>Encapsulates SMAPI's JSON parsing.</summary> - public JsonHelper JsonHelper { get; } = new JsonHelper(); - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - public ModToolkit() - { - ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version); - this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}"; - } - - /// <summary>Extract mod metadata from the wiki compatibility list.</summary> - public async Task<WikiModList> GetWikiCompatibilityListAsync() - { - var client = new WikiClient(this.UserAgent); - return await client.FetchModsAsync(); - } - - /// <summary>Get SMAPI's internal mod database.</summary> - /// <param name="metadataPath">The file path for the SMAPI metadata file.</param> - public ModDatabase GetModDatabase(string metadataPath) - { - MetadataModel metadata = JsonConvert.DeserializeObject<MetadataModel>(File.ReadAllText(metadataPath)); - ModDataRecord[] records = metadata.ModData.Select(pair => new ModDataRecord(pair.Key, pair.Value)).ToArray(); - return new ModDatabase(records, this.GetUpdateUrl); - } - - /// <summary>Extract information about all mods in the given folder.</summary> - /// <param name="rootPath">The root folder containing mods.</param> - public IEnumerable<ModFolder> GetModFolders(string rootPath) - { - return new ModScanner(this.JsonHelper).GetModFolders(rootPath); - } - - /// <summary>Get an update URL for an update key (if valid).</summary> - /// <param name="updateKey">The update key.</param> - public string GetUpdateUrl(string updateKey) - { - string[] parts = updateKey.Split(new[] { ':' }, 2); - if (parts.Length != 2) - return null; - - string vendorKey = parts[0].Trim(); - string modID = parts[1].Trim(); - - if (this.VendorModUrls.TryGetValue(vendorKey, out string urlTemplate)) - return string.Format(urlTemplate, modID); - - return null; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs b/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs deleted file mode 100644 index 1bb19e8c..00000000 --- a/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: AssemblyTitle("SMAPI.Toolkit")] -[assembly: AssemblyDescription("A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.")] -[assembly: InternalsVisibleTo("StardewModdingAPI")] -[assembly: InternalsVisibleTo("StardewModdingAPI.Web")] diff --git a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs deleted file mode 100644 index ba9ca6c6..00000000 --- a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -namespace StardewModdingAPI.Toolkit -{ - /// <summary>A semantic version with an optional release tag.</summary> - /// <remarks> - /// The implementation is defined by Semantic Version 2.0 (https://semver.org/), with a few deviations: - /// - short-form "x.y" versions are supported (equivalent to "x.y.0"); - /// - hyphens are synonymous with dots in prerelease tags (like "-unofficial.3-pathoschild"); - /// - +build suffixes are not supported; - /// - and "-unofficial" in prerelease tags is always lower-precedence (e.g. "1.0-beta" is newer than "1.0-unofficial"). - /// </remarks> - public class SemanticVersion : ISemanticVersion - { - /********* - ** Fields - *********/ - /// <summary>A regex pattern matching a valid prerelease tag.</summary> - internal const string TagPattern = @"(?>[a-z0-9]+[\-\.]?)+"; - - /// <summary>A regex pattern matching a version within a larger string.</summary> - internal const string UnboundedVersionPattern = @"(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>" + SemanticVersion.TagPattern + "))?"; - - /// <summary>A regular expression matching a semantic version string.</summary> - /// <remarks>This pattern is derived from the BNF documentation in the <a href="https://github.com/mojombo/semver">semver repo</a>, with deviations to support the Stardew Valley mod conventions (see remarks on <see cref="SemanticVersion"/>).</remarks> - internal static readonly Regex Regex = new Regex($@"^{SemanticVersion.UnboundedVersionPattern}$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); - - - /********* - ** Accessors - *********/ - /// <summary>The major version incremented for major API changes.</summary> - public int MajorVersion { get; } - - /// <summary>The minor version incremented for backwards-compatible changes.</summary> - public int MinorVersion { get; } - - /// <summary>The patch version for backwards-compatible bug fixes.</summary> - public int PatchVersion { get; } - - /// <summary>An optional prerelease tag.</summary> - public string PrereleaseTag { get; } - -#if !SMAPI_3_0_STRICT - /// <summary>An optional prerelease tag.</summary> - [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")] - public string Build => this.PrereleaseTag; - - /// <summary>Whether the version was parsed from the legacy object format.</summary> - public bool IsLegacyFormat { get; } -#endif - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="major">The major version incremented for major API changes.</param> - /// <param name="minor">The minor version incremented for backwards-compatible changes.</param> - /// <param name="patch">The patch version for backwards-compatible fixes.</param> - /// <param name="prereleaseTag">An optional prerelease tag.</param> - /// <param name="isLegacyFormat">Whether the version was parsed from the legacy object format.</param> - public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null -#if !SMAPI_3_0_STRICT - , bool isLegacyFormat = false -#endif - ) - { - this.MajorVersion = major; - this.MinorVersion = minor; - this.PatchVersion = patch; - this.PrereleaseTag = this.GetNormalisedTag(prereleaseTag); -#if !SMAPI_3_0_STRICT - this.IsLegacyFormat = isLegacyFormat; -#endif - - this.AssertValid(); - } - - /// <summary>Construct an instance.</summary> - /// <param name="version">The assembly version.</param> - /// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception> - public SemanticVersion(Version version) - { - if (version == null) - throw new ArgumentNullException(nameof(version), "The input version can't be null."); - - this.MajorVersion = version.Major; - this.MinorVersion = version.Minor; - this.PatchVersion = version.Build; - - this.AssertValid(); - } - - /// <summary>Construct an instance.</summary> - /// <param name="version">The semantic version string.</param> - /// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception> - /// <exception cref="FormatException">The <paramref name="version"/> is not a valid semantic version.</exception> - public SemanticVersion(string version) - { - // parse - if (version == null) - throw new ArgumentNullException(nameof(version), "The input version string can't be null."); - var match = SemanticVersion.Regex.Match(version.Trim()); - if (!match.Success) - throw new FormatException($"The input '{version}' isn't a valid semantic version."); - - // initialise - this.MajorVersion = int.Parse(match.Groups["major"].Value); - this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; - this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; - this.PrereleaseTag = match.Groups["prerelease"].Success ? this.GetNormalisedTag(match.Groups["prerelease"].Value) : null; - - this.AssertValid(); - } - - /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> - /// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception> - public int CompareTo(ISemanticVersion other) - { - if (other == null) - throw new ArgumentNullException(nameof(other)); - return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.PrereleaseTag); - } - - /// <summary>Indicates whether the current object is equal to another object of the same type.</summary> - /// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns> - /// <param name="other">An object to compare with this object.</param> - public bool Equals(ISemanticVersion other) - { - return other != null && this.CompareTo(other) == 0; - } - - /// <summary>Whether this is a pre-release version.</summary> - public bool IsPrerelease() - { - return !string.IsNullOrWhiteSpace(this.PrereleaseTag); - } - - /// <summary>Get whether this version is older than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> - public bool IsOlderThan(ISemanticVersion other) - { - return this.CompareTo(other) < 0; - } - - /// <summary>Get whether this version is older than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> - /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception> - public bool IsOlderThan(string other) - { - return this.IsOlderThan(new SemanticVersion(other)); - } - - /// <summary>Get whether this version is newer than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> - public bool IsNewerThan(ISemanticVersion other) - { - return this.CompareTo(other) > 0; - } - - /// <summary>Get whether this version is newer than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> - /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception> - public bool IsNewerThan(string other) - { - return this.IsNewerThan(new SemanticVersion(other)); - } - - /// <summary>Get whether this version is between two specified versions (inclusively).</summary> - /// <param name="min">The minimum version.</param> - /// <param name="max">The maximum version.</param> - public bool IsBetween(ISemanticVersion min, ISemanticVersion max) - { - return this.CompareTo(min) >= 0 && this.CompareTo(max) <= 0; - } - - /// <summary>Get whether this version is between two specified versions (inclusively).</summary> - /// <param name="min">The minimum version.</param> - /// <param name="max">The maximum version.</param> - /// <exception cref="FormatException">One of the specified versions is not a valid semantic version.</exception> - public bool IsBetween(string min, string max) - { - return this.IsBetween(new SemanticVersion(min), new SemanticVersion(max)); - } - - /// <summary>Get a string representation of the version.</summary> - public override string ToString() - { - // version - string result = this.PatchVersion != 0 - ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}" - : $"{this.MajorVersion}.{this.MinorVersion}"; - - // tag - string tag = this.PrereleaseTag; - if (tag != null) - result += $"-{tag}"; - return result; - } - - /// <summary>Parse a version string without throwing an exception if it fails.</summary> - /// <param name="version">The version string.</param> - /// <param name="parsed">The parsed representation.</param> - /// <returns>Returns whether parsing the version succeeded.</returns> - public static bool TryParse(string version, out ISemanticVersion parsed) - { - try - { - parsed = new SemanticVersion(version); - return true; - } - catch - { - parsed = null; - return false; - } - } - - - /********* - ** Private methods - *********/ - /// <summary>Get a normalised build tag.</summary> - /// <param name="tag">The tag to normalise.</param> - private string GetNormalisedTag(string tag) - { - tag = tag?.Trim(); - return !string.IsNullOrWhiteSpace(tag) ? tag : null; - } - - /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary> - /// <param name="otherMajor">The major version to compare with this instance.</param> - /// <param name="otherMinor">The minor version to compare with this instance.</param> - /// <param name="otherPatch">The patch version to compare with this instance.</param> - /// <param name="otherTag">The prerelease tag to compare with this instance.</param> - private int CompareTo(int otherMajor, int otherMinor, int otherPatch, string otherTag) - { - const int same = 0; - const int curNewer = 1; - const int curOlder = -1; - - // compare stable versions - if (this.MajorVersion != otherMajor) - return this.MajorVersion.CompareTo(otherMajor); - if (this.MinorVersion != otherMinor) - return this.MinorVersion.CompareTo(otherMinor); - if (this.PatchVersion != otherPatch) - return this.PatchVersion.CompareTo(otherPatch); - if (this.PrereleaseTag == otherTag) - return same; - - // stable supercedes pre-release - bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag); - bool otherIsStable = string.IsNullOrWhiteSpace(otherTag); - if (curIsStable) - return curNewer; - if (otherIsStable) - return curOlder; - - // compare two pre-release tag values - string[] curParts = this.PrereleaseTag.Split('.', '-'); - string[] otherParts = otherTag.Split('.', '-'); - for (int i = 0; i < curParts.Length; i++) - { - // longer prerelease tag supercedes if otherwise equal - if (otherParts.Length <= i) - return curNewer; - - // compare if different - if (curParts[i] != otherParts[i]) - { - // unofficial is always lower-precedence - if (otherParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase)) - return curNewer; - if (curParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase)) - return curOlder; - - // compare numerically if possible - { - if (int.TryParse(curParts[i], out int curNum) && int.TryParse(otherParts[i], out int otherNum)) - return curNum.CompareTo(otherNum); - } - - // else compare lexically - return string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase); - } - } - - // fallback (this should never happen) - return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase); - } - - /// <summary>Assert that the current version is valid.</summary> - private void AssertValid() - { - if (this.MajorVersion < 0 || this.MinorVersion < 0 || this.PatchVersion < 0) - throw new FormatException($"{this} isn't a valid semantic version. The major, minor, and patch numbers can't be negative."); - if (this.MajorVersion == 0 && this.MinorVersion == 0 && this.PatchVersion == 0) - throw new FormatException($"{this} isn't a valid semantic version. At least one of the major, minor, and patch numbers must be more than zero."); - if (this.PrereleaseTag != null) - { - if (this.PrereleaseTag.Trim() == "") - throw new FormatException($"{this} isn't a valid semantic version. The tag cannot be a blank string (but may be omitted)."); - if (!Regex.IsMatch(this.PrereleaseTag, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase)) - throw new FormatException($"{this} isn't a valid semantic version. The tag is invalid."); - } - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs deleted file mode 100644 index 232c22a7..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using Newtonsoft.Json; -using StardewModdingAPI.Toolkit.Serialisation.Models; - -namespace StardewModdingAPI.Toolkit.Serialisation.Converters -{ - /// <summary>Handles deserialisation of <see cref="ManifestContentPackFor"/> arrays.</summary> - public class ManifestContentPackForConverter : JsonConverter - { - /********* - ** Accessors - *********/ - /// <summary>Whether this converter can write JSON.</summary> - public override bool CanWrite => false; - - - /********* - ** Public methods - *********/ - /// <summary>Get whether this instance can convert the specified object type.</summary> - /// <param name="objectType">The object type.</param> - public override bool CanConvert(Type objectType) - { - return objectType == typeof(ManifestContentPackFor[]); - } - - - /********* - ** Protected methods - *********/ - /// <summary>Read the JSON representation of the object.</summary> - /// <param name="reader">The JSON reader.</param> - /// <param name="objectType">The object type.</param> - /// <param name="existingValue">The object being read.</param> - /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return serializer.Deserialize<ManifestContentPackFor>(reader); - } - - /// <summary>Writes the JSON representation of the object.</summary> - /// <param name="writer">The JSON writer.</param> - /// <param name="value">The value.</param> - /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs deleted file mode 100644 index 0a304ee3..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Toolkit.Serialisation.Models; - -namespace StardewModdingAPI.Toolkit.Serialisation.Converters -{ - /// <summary>Handles deserialisation of <see cref="ManifestDependency"/> arrays.</summary> - internal class ManifestDependencyArrayConverter : JsonConverter - { - /********* - ** Accessors - *********/ - /// <summary>Whether this converter can write JSON.</summary> - public override bool CanWrite => false; - - - /********* - ** Public methods - *********/ - /// <summary>Get whether this instance can convert the specified object type.</summary> - /// <param name="objectType">The object type.</param> - public override bool CanConvert(Type objectType) - { - return objectType == typeof(ManifestDependency[]); - } - - - /********* - ** Protected methods - *********/ - /// <summary>Read the JSON representation of the object.</summary> - /// <param name="reader">The JSON reader.</param> - /// <param name="objectType">The object type.</param> - /// <param name="existingValue">The object being read.</param> - /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - List<ManifestDependency> result = new List<ManifestDependency>(); - foreach (JObject obj in JArray.Load(reader).Children<JObject>()) - { - string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID)); - string minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion)); - bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true; - result.Add(new ManifestDependency(uniqueID, minVersion, required)); - } - return result.ToArray(); - } - - /// <summary>Writes the JSON representation of the object.</summary> - /// <param name="writer">The JSON writer.</param> - /// <param name="value">The value.</param> - /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs deleted file mode 100644 index aca06849..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace StardewModdingAPI.Toolkit.Serialisation.Converters -{ - /// <summary>Handles deserialisation of <see cref="ISemanticVersion"/>.</summary> - internal class SemanticVersionConverter : JsonConverter - { - /********* - ** Accessors - *********/ - /// <summary>Get whether this converter can read JSON.</summary> - public override bool CanRead => true; - - /// <summary>Get whether this converter can write JSON.</summary> - public override bool CanWrite => true; - - - /********* - ** Public methods - *********/ - /// <summary>Get whether this instance can convert the specified object type.</summary> - /// <param name="objectType">The object type.</param> - public override bool CanConvert(Type objectType) - { - return typeof(ISemanticVersion).IsAssignableFrom(objectType); - } - - /// <summary>Reads the JSON representation of the object.</summary> - /// <param name="reader">The JSON reader.</param> - /// <param name="objectType">The object type.</param> - /// <param name="existingValue">The object being read.</param> - /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - string path = reader.Path; - switch (reader.TokenType) - { - case JsonToken.StartObject: - return this.ReadObject(JObject.Load(reader)); - case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value<string>(), path); - default: - throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path})."); - } - } - - /// <summary>Writes the JSON representation of the object.</summary> - /// <param name="writer">The JSON writer.</param> - /// <param name="value">The value.</param> - /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(value?.ToString()); - } - - - /********* - ** Private methods - *********/ - /// <summary>Read a JSON object.</summary> - /// <param name="obj">The JSON object to read.</param> - private ISemanticVersion ReadObject(JObject obj) - { - int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion)); - int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion)); - int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion)); - string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag)); -#if !SMAPI_3_0_STRICT - if (string.IsNullOrWhiteSpace(prereleaseTag)) - { - prereleaseTag = obj.ValueIgnoreCase<string>("Build"); - if (prereleaseTag == "0") - prereleaseTag = null; // '0' from incorrect examples in old SMAPI documentation - } -#endif - - return new SemanticVersion(major, minor, patch, prereleaseTag -#if !SMAPI_3_0_STRICT - , isLegacyFormat: true -#endif - ); - } - - /// <summary>Read a JSON string.</summary> - /// <param name="str">The JSON string value.</param> - /// <param name="path">The path to the current JSON node.</param> - private ISemanticVersion ReadString(string str, string path) - { - if (string.IsNullOrWhiteSpace(str)) - return null; - if (!SemanticVersion.TryParse(str, out ISemanticVersion version)) - throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path})."); - return version; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs deleted file mode 100644 index 5e0b0f4a..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace StardewModdingAPI.Toolkit.Serialisation.Converters -{ - /// <summary>The base implementation for simplified converters which deserialise <typeparamref name="T"/> without overriding serialisation.</summary> - /// <typeparam name="T">The type to deserialise.</typeparam> - internal abstract class SimpleReadOnlyConverter<T> : JsonConverter - { - /********* - ** Accessors - *********/ - /// <summary>Whether this converter can write JSON.</summary> - public override bool CanWrite => false; - - - /********* - ** Public methods - *********/ - /// <summary>Get whether this instance can convert the specified object type.</summary> - /// <param name="objectType">The object type.</param> - public override bool CanConvert(Type objectType) - { - return objectType == typeof(T); - } - - /// <summary>Writes the JSON representation of the object.</summary> - /// <param name="writer">The JSON writer.</param> - /// <param name="value">The value.</param> - /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - - /// <summary>Reads the JSON representation of the object.</summary> - /// <param name="reader">The JSON reader.</param> - /// <param name="objectType">The object type.</param> - /// <param name="existingValue">The object being read.</param> - /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - string path = reader.Path; - switch (reader.TokenType) - { - case JsonToken.StartObject: - return this.ReadObject(JObject.Load(reader), path); - case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value<string>(), path); - default: - throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path})."); - } - } - - - /********* - ** Protected methods - *********/ - /// <summary>Read a JSON object.</summary> - /// <param name="obj">The JSON object to read.</param> - /// <param name="path">The path to the current JSON node.</param> - protected virtual T ReadObject(JObject obj, string path) - { - throw new SParseException($"Can't parse {typeof(T).Name} from object node (path: {path})."); - } - - /// <summary>Read a JSON string.</summary> - /// <param name="str">The JSON string value.</param> - /// <param name="path">The path to the current JSON node.</param> - protected virtual T ReadString(string str, string path) - { - throw new SParseException($"Can't parse {typeof(T).Name} from string node (path: {path})."); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs b/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs deleted file mode 100644 index 12b2c933..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; - -namespace StardewModdingAPI.Toolkit.Serialisation -{ - /// <summary>Provides extension methods for parsing JSON.</summary> - public static class JsonExtensions - { - /// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary> - /// <typeparam name="T">The value type.</typeparam> - /// <param name="obj">The JSON object to search.</param> - /// <param name="fieldName">The field name.</param> - public static T ValueIgnoreCase<T>(this JObject obj, string fieldName) - { - JToken token = obj.GetValue(fieldName, StringComparison.InvariantCultureIgnoreCase); - return token != null - ? token.Value<T>() - : default(T); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs deleted file mode 100644 index cf2ce0d1..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using StardewModdingAPI.Toolkit.Serialisation.Converters; - -namespace StardewModdingAPI.Toolkit.Serialisation -{ - /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> - public class JsonHelper - { - /********* - ** Accessors - *********/ - /// <summary>The JSON settings to use when serialising and deserialising files.</summary> - public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded - Converters = new List<JsonConverter> - { - new SemanticVersionConverter(), - new StringEnumConverter() - } - }; - - - /********* - ** Public methods - *********/ - /// <summary>Read a JSON file.</summary> - /// <typeparam name="TModel">The model type.</typeparam> - /// <param name="fullPath">The absolete file path.</param> - /// <param name="result">The parsed content model.</param> - /// <returns>Returns false if the file doesn't exist, else true.</returns> - /// <exception cref="ArgumentException">The given <paramref name="fullPath"/> is empty or invalid.</exception> - /// <exception cref="JsonReaderException">The file contains invalid JSON.</exception> - public bool ReadJsonFileIfExists<TModel>(string fullPath, out TModel result) - { - // validate - if (string.IsNullOrWhiteSpace(fullPath)) - throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); - - // read file - string json; - try - { - json = File.ReadAllText(fullPath); - } - catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) - { - result = default(TModel); - return false; - } - - // deserialise model - try - { - result = this.Deserialise<TModel>(json); - return true; - } - catch (Exception ex) - { - string error = $"Can't parse JSON file at {fullPath}."; - - if (ex is JsonReaderException) - { - error += " This doesn't seem to be valid JSON."; - if (json.Contains("“") || json.Contains("”")) - error += " Found curly quotes in the text; note that only straight quotes are allowed in JSON."; - } - error += $"\nTechnical details: {ex.Message}"; - throw new JsonReaderException(error); - } - } - - /// <summary>Save to a JSON file.</summary> - /// <typeparam name="TModel">The model type.</typeparam> - /// <param name="fullPath">The absolete file path.</param> - /// <param name="model">The model to save.</param> - /// <exception cref="InvalidOperationException">The given path is empty or invalid.</exception> - public void WriteJsonFile<TModel>(string fullPath, TModel model) - where TModel : class - { - // validate - if (string.IsNullOrWhiteSpace(fullPath)) - throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); - - // create directory if needed - string dir = Path.GetDirectoryName(fullPath); - if (dir == null) - throw new ArgumentException("The file path is invalid.", nameof(fullPath)); - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - // write file - string json = this.Serialise(model); - File.WriteAllText(fullPath, json); - } - - /// <summary>Deserialize JSON text if possible.</summary> - /// <typeparam name="TModel">The model type.</typeparam> - /// <param name="json">The raw JSON text.</param> - public TModel Deserialise<TModel>(string json) - { - try - { - return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings); - } - catch (JsonReaderException) - { - // try replacing curly quotes - if (json.Contains("“") || json.Contains("”")) - { - try - { - return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings); - } - catch { /* rethrow original error */ } - } - - throw; - } - } - - /// <summary>Serialize a model to JSON text.</summary> - /// <typeparam name="TModel">The model type.</typeparam> - /// <param name="model">The model to serialise.</param> - /// <param name="formatting">The formatting to apply.</param> - public string Serialise<TModel>(TModel model, Formatting formatting = Formatting.Indented) - { - return JsonConvert.SerializeObject(model, formatting, this.JsonSettings); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs deleted file mode 100644 index 6cb9496b..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; -using StardewModdingAPI.Toolkit.Serialisation.Converters; - -namespace StardewModdingAPI.Toolkit.Serialisation.Models -{ - /// <summary>A manifest which describes a mod for SMAPI.</summary> - public class Manifest : IManifest - { - /********* - ** Accessors - *********/ - /// <summary>The mod name.</summary> - public string Name { get; set; } - - /// <summary>A brief description of the mod.</summary> - public string Description { get; set; } - - /// <summary>The mod author's name.</summary> - public string Author { get; set; } - - /// <summary>The mod version.</summary> - public ISemanticVersion Version { get; set; } - - /// <summary>The minimum SMAPI version required by this mod, if any.</summary> - public ISemanticVersion MinimumApiVersion { get; set; } - - /// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary> - public string EntryDll { get; set; } - - /// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="Manifest.EntryDll"/>.</summary> - [JsonConverter(typeof(ManifestContentPackForConverter))] - public IManifestContentPackFor ContentPackFor { get; set; } - - /// <summary>The other mods that must be loaded before this mod.</summary> - [JsonConverter(typeof(ManifestDependencyArrayConverter))] - public IManifestDependency[] Dependencies { get; set; } - - /// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary> - public string[] UpdateKeys { get; set; } - - /// <summary>The unique mod ID.</summary> - public string UniqueID { get; set; } - - /// <summary>Any manifest fields which didn't match a valid field.</summary> - [JsonExtensionData] - public IDictionary<string, object> ExtraFields { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - public Manifest() { } - - /// <summary>Construct an instance for a transitional content pack.</summary> - /// <param name="uniqueID">The unique mod ID.</param> - /// <param name="name">The mod name.</param> - /// <param name="author">The mod author's name.</param> - /// <param name="description">A brief description of the mod.</param> - /// <param name="version">The mod version.</param> - /// <param name="contentPackFor">The modID which will read this as a content pack.</param> - public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string contentPackFor = null) - { - this.Name = name; - this.Author = author; - this.Description = description; - this.Version = version; - this.UniqueID = uniqueID; - this.UpdateKeys = new string[0]; - this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor }; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs deleted file mode 100644 index d0e42216..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Serialisation.Models -{ - /// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary> - public class ManifestContentPackFor : IManifestContentPackFor - { - /********* - ** Accessors - *********/ - /// <summary>The unique ID of the mod which can read this content pack.</summary> - public string UniqueID { get; set; } - - /// <summary>The minimum required version (if any).</summary> - public ISemanticVersion MinimumVersion { get; set; } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs deleted file mode 100644 index 8db58d5d..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Serialisation.Models -{ - /// <summary>A mod dependency listed in a mod manifest.</summary> - public class ManifestDependency : IManifestDependency - { - /********* - ** Accessors - *********/ - /// <summary>The unique mod ID to require.</summary> - public string UniqueID { get; set; } - - /// <summary>The minimum required version (if any).</summary> - public ISemanticVersion MinimumVersion { get; set; } - - /// <summary>Whether the dependency must be installed to use the mod.</summary> - public bool IsRequired { get; set; } - - - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="uniqueID">The unique mod ID to require.</param> - /// <param name="minimumVersion">The minimum required version (if any).</param> - /// <param name="required">Whether the dependency must be installed to use the mod.</param> - public ManifestDependency(string uniqueID, string minimumVersion, bool required = true) - { - this.UniqueID = uniqueID; - this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion) - ? new SemanticVersion(minimumVersion) - : null; - this.IsRequired = required; - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs b/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs deleted file mode 100644 index 61a7b305..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace StardewModdingAPI.Toolkit.Serialisation -{ - /// <summary>A format exception which provides a user-facing error message.</summary> - internal class SParseException : FormatException - { - /********* - ** Public methods - *********/ - /// <summary>Construct an instance.</summary> - /// <param name="message">The error message.</param> - /// <param name="ex">The underlying exception, if any.</param> - public SParseException(string message, Exception ex = null) - : base(message, ex) { } - } -} diff --git a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj b/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj deleted file mode 100644 index 351b36b6..00000000 --- a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj +++ /dev/null @@ -1,27 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks> - <GenerateAssemblyInfo>false</GenerateAssemblyInfo> - <OutputPath>..\..\bin\$(Configuration)\SMAPI.Toolkit</OutputPath> - <DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\StardewModdingAPI.Toolkit.xml</DocumentationFile> - <LangVersion>latest</LangVersion> - </PropertyGroup> - - <ItemGroup> - <Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" /> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="HtmlAgilityPack" Version="1.8.9" /> - <PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> - <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.2.0" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\StardewModdingAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj" /> - </ItemGroup> - - <Import Project="..\..\build\common.targets" /> - -</Project> diff --git a/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs deleted file mode 100644 index 7856fdb1..00000000 --- a/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.IO; -using System.Threading; - -namespace StardewModdingAPI.Toolkit.Utilities -{ - /// <summary>Provides utilities for dealing with files.</summary> - public static class FileUtilities - { - /********* - ** Public methods - *********/ - /// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary> - /// <param name="entry">The file or folder to reset.</param> - public static void ForceDelete(FileSystemInfo entry) - { - // ignore if already deleted - entry.Refresh(); - if (!entry.Exists) - return; - - // delete children - if (entry is DirectoryInfo folder) - { - foreach (FileSystemInfo child in folder.GetFileSystemInfos()) - FileUtilities.ForceDelete(child); - } - - // reset permissions & delete - entry.Attributes = FileAttributes.Normal; - entry.Delete(); - - // wait for deletion to finish - for (int i = 0; i < 10; i++) - { - entry.Refresh(); - if (entry.Exists) - Thread.Sleep(500); - } - - // throw exception if deletion didn't happen before timeout - entry.Refresh(); - if (entry.Exists) - throw new IOException($"Timed out trying to delete {entry.FullName}"); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs deleted file mode 100644 index 8a3c2b03..00000000 --- a/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Diagnostics.Contracts; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; - -namespace StardewModdingAPI.Toolkit.Utilities -{ - /// <summary>Provides utilities for normalising file paths.</summary> - public static class PathUtilities - { - /********* - ** Fields - *********/ - /// <summary>The possible directory separator characters in a file path.</summary> - private static readonly char[] PossiblePathSeparators = new[] { '/', '\\', Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }.Distinct().ToArray(); - - /// <summary>The preferred directory separator chaeacter in an asset key.</summary> - private static readonly string PreferredPathSeparator = Path.DirectorySeparatorChar.ToString(); - - - /********* - ** Public methods - *********/ - /// <summary>Get the segments from a path (e.g. <c>/usr/bin/boop</c> => <c>usr</c>, <c>bin</c>, and <c>boop</c>).</summary> - /// <param name="path">The path to split.</param> - /// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param> - public static string[] GetSegments(string path, int? limit = null) - { - return limit.HasValue - ? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, StringSplitOptions.RemoveEmptyEntries) - : path.Split(PathUtilities.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); - } - - /// <summary>Normalise path separators in a file path.</summary> - /// <param name="path">The file path to normalise.</param> - [Pure] - public static string NormalisePathSeparators(string path) - { - string[] parts = PathUtilities.GetSegments(path); - string normalised = string.Join(PathUtilities.PreferredPathSeparator, parts); - if (path.StartsWith(PathUtilities.PreferredPathSeparator)) - normalised = PathUtilities.PreferredPathSeparator + normalised; // keep root slash - return normalised; - } - - /// <summary>Get a directory or file path relative to a given source path.</summary> - /// <param name="sourceDir">The source folder path.</param> - /// <param name="targetPath">The target folder or file path.</param> - [Pure] - public static string GetRelativePath(string sourceDir, string targetPath) - { - // convert to URIs - Uri from = new Uri(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); - Uri to = new Uri(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); - if (from.Scheme != to.Scheme) - throw new InvalidOperationException($"Can't get path for '{targetPath}' relative to '{sourceDir}'."); - - // get relative path - string relative = PathUtilities.NormalisePathSeparators(Uri.UnescapeDataString(from.MakeRelativeUri(to).ToString())); - if (relative == "") - relative = "./"; - return relative; - } - - /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary> - /// <param name="path">The path to check.</param> - public static bool IsSafeRelativePath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - return true; - - return - !Path.IsPathRooted(path) - && PathUtilities.GetSegments(path).All(segment => segment.Trim() != ".."); - } - - /// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary> - /// <param name="str">The string to check.</param> - public static bool IsSlug(string str) - { - return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase); - } - } -} |