diff options
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/IModMetadata.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModData/ModDataField.cs (renamed from src/SMAPI/Framework/Models/ModDataField.cs) | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModData/ModDataFieldKey.cs (renamed from src/SMAPI/Framework/Models/ModDataFieldKey.cs) | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModData/ModDataRecord.cs (renamed from src/SMAPI/Framework/Models/ModDataRecord.cs) | 104 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModData/ModDatabase.cs | 168 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModData/ModStatus.cs (renamed from src/SMAPI/Framework/Models/ModStatus.cs) | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModData/ParsedModDataRecord.cs (renamed from src/SMAPI/Framework/Models/ParsedModDataRecord.cs) | 5 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModMetadata.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModResolver.cs | 48 | ||||
-rw-r--r-- | src/SMAPI/Framework/Models/SConfig.cs | 5 |
10 files changed, 208 insertions, 132 deletions
diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index 41484567..a91b0a5b 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -1,4 +1,4 @@ -using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.ModLoading; namespace StardewModdingAPI.Framework diff --git a/src/SMAPI/Framework/Models/ModDataField.cs b/src/SMAPI/Framework/ModData/ModDataField.cs index 0812b39b..fa8dd6d0 100644 --- a/src/SMAPI/Framework/Models/ModDataField.cs +++ b/src/SMAPI/Framework/ModData/ModDataField.cs @@ -1,6 +1,6 @@ using System.Linq; -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Framework.ModData { /// <summary>A versioned mod metadata field.</summary> internal class ModDataField diff --git a/src/SMAPI/Framework/Models/ModDataFieldKey.cs b/src/SMAPI/Framework/ModData/ModDataFieldKey.cs index 5767afc9..f68f575c 100644 --- a/src/SMAPI/Framework/Models/ModDataFieldKey.cs +++ b/src/SMAPI/Framework/ModData/ModDataFieldKey.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Framework.ModData { /// <summary>The valid field keys.</summary> public enum ModDataFieldKey diff --git a/src/SMAPI/Framework/Models/ModDataRecord.cs b/src/SMAPI/Framework/ModData/ModDataRecord.cs index 2c26741c..79a954f7 100644 --- a/src/SMAPI/Framework/Models/ModDataRecord.cs +++ b/src/SMAPI/Framework/ModData/ModDataRecord.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Framework.ModData { /// <summary>Raw mod metadata from SMAPI's internal mod list.</summary> internal class ModDataRecord @@ -63,47 +62,6 @@ namespace StardewModdingAPI.Framework.Models /********* ** Public methods *********/ - /// <summary>Get whether the manifest matches the <see cref="FormerIDs"/> field.</summary> - /// <param name="manifest">The mod manifest to check.</param> - public bool Matches(IManifest manifest) - { - // try main ID - if (this.ID != null && this.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase)) - return true; - - // try former IDs - if (this.FormerIDs != null) - { - foreach (string part in this.FormerIDs.Split('|')) - { - // packed field snapshot - if (part.StartsWith("{")) - { - FieldSnapshot snapshot = JsonConvert.DeserializeObject<FieldSnapshot>(part); - bool isMatch = - (snapshot.ID == null || snapshot.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase)) - && (snapshot.EntryDll == null || snapshot.EntryDll.Equals(manifest.EntryDll, StringComparison.InvariantCultureIgnoreCase)) - && ( - snapshot.Author == null - || snapshot.Author.Equals(manifest.Author, StringComparison.InvariantCultureIgnoreCase) - || (manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase)) - ) - && (snapshot.Name == null || snapshot.Name.Equals(manifest.Name, StringComparison.InvariantCultureIgnoreCase)); - - if (isMatch) - return true; - } - - // plain ID - else if (part.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase)) - return true; - } - } - - // no match - return false; - } - /// <summary>Get a parsed representation of the <see cref="Fields"/>.</summary> public IEnumerable<ModDataField> GetFields() { @@ -146,41 +104,6 @@ namespace StardewModdingAPI.Framework.Models } } - /// <summary>Get a parsed representation of the <see cref="Fields"/> which match a given manifest.</summary> - /// <param name="manifest">The manifest to match.</param> - public ParsedModDataRecord ParseFieldsFor(IManifest manifest) - { - ParsedModDataRecord parsed = new ParsedModDataRecord { DataRecord = this }; - foreach (ModDataField field in this.GetFields().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; - } - /// <summary>Get a semantic local version for update checks.</summary> /// <param name="version">The remote version to normalise.</param> public string GetLocalVersionForUpdateChecks(string version) @@ -214,30 +137,5 @@ namespace StardewModdingAPI.Framework.Models this.ExtensionData = null; } } - - - /********* - ** Private models - *********/ - /// <summary>A unique set of fields which identifies the mod.</summary> - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialisation.")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialisation.")] - private class FieldSnapshot - { - /********* - ** Accessors - *********/ - /// <summary>The unique mod ID (or <c>null</c> to ignore it).</summary> - public string ID { get; set; } - - /// <summary>The entry DLL (or <c>null</c> to ignore it).</summary> - public string EntryDll { get; set; } - - /// <summary>The mod name (or <c>null</c> to ignore it).</summary> - public string Name { get; set; } - - /// <summary>The author name (or <c>null</c> to ignore it).</summary> - public string Author { get; set; } - } } } diff --git a/src/SMAPI/Framework/ModData/ModDatabase.cs b/src/SMAPI/Framework/ModData/ModDatabase.cs new file mode 100644 index 00000000..af067f8f --- /dev/null +++ b/src/SMAPI/Framework/ModData/ModDatabase.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Newtonsoft.Json; + +namespace StardewModdingAPI.Framework.ModData +{ + /// <summary>Handles access to SMAPI's internal mod metadata list.</summary> + internal class ModDatabase + { + /********* + ** Properties + *********/ + /// <summary>The underlying mod data records indexed by default display name.</summary> + private readonly IDictionary<string, ModDataRecord> Records; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an empty instance.</summary> + public ModDatabase() + : this(new Dictionary<string, ModDataRecord>()) { } + + /// <summary>Construct an instance.</summary> + /// <param name="records">The underlying mod data records indexed by default display name.</param> + public ModDatabase(IDictionary<string, ModDataRecord> records) + { + this.Records = records; + } + + /// <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 ParsedModDataRecord GetParsed(IManifest manifest) + { + // get raw record + if (!this.TryGetRaw(manifest, out string displayName, out ModDataRecord record)) + return null; + + // parse fields + ParsedModDataRecord parsed = new ParsedModDataRecord { DisplayName = displayName, DataRecord = record }; + foreach (ModDataField field in record.GetFields().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; + } + + /// <summary>Get the display name for a given mod ID (if available).</summary> + /// <param name="id">The unique mod ID.</param> + public string GetDisplayNameFor(string id) + { + foreach (var entry in this.Records) + { + if (entry.Value.ID != null && entry.Value.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) + return entry.Key; + } + + return null; + } + + + /********* + ** Private models + *********/ + /// <summary>Get the data record matching a given manifest.</summary> + /// <param name="manifest">The mod manifest.</param> + /// <param name="displayName">The mod's default display name.</param> + /// <param name="record">The raw mod record.</param> + private bool TryGetRaw(IManifest manifest, out string displayName, out ModDataRecord record) + { + if (manifest != null) + { + foreach (var entry in this.Records) + { + displayName = entry.Key; + record = entry.Value; + + // try main ID + if (record.ID != null && record.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase)) + return true; + + // try former IDs + if (record.FormerIDs != null) + { + foreach (string part in record.FormerIDs.Split('|')) + { + // packed field snapshot + if (part.StartsWith("{")) + { + FieldSnapshot snapshot = JsonConvert.DeserializeObject<FieldSnapshot>(part); + bool isMatch = + (snapshot.ID == null || snapshot.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase)) + && (snapshot.EntryDll == null || snapshot.EntryDll.Equals(manifest.EntryDll, StringComparison.InvariantCultureIgnoreCase)) + && ( + snapshot.Author == null + || snapshot.Author.Equals(manifest.Author, StringComparison.InvariantCultureIgnoreCase) + || (manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase)) + ) + && (snapshot.Name == null || snapshot.Name.Equals(manifest.Name, StringComparison.InvariantCultureIgnoreCase)); + + if (isMatch) + return true; + } + + // plain ID + else if (part.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase)) + return true; + } + } + } + } + + displayName = null; + record = null; + return false; + } + + + /********* + ** Private models + *********/ + /// <summary>A unique set of fields which identifies the mod.</summary> + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialisation.")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialisation.")] + private class FieldSnapshot + { + /********* + ** Accessors + *********/ + /// <summary>The unique mod ID (or <c>null</c> to ignore it).</summary> + public string ID { get; set; } + + /// <summary>The entry DLL (or <c>null</c> to ignore it).</summary> + public string EntryDll { get; set; } + + /// <summary>The mod name (or <c>null</c> to ignore it).</summary> + public string Name { get; set; } + + /// <summary>The author name (or <c>null</c> to ignore it).</summary> + public string Author { get; set; } + } + } +} diff --git a/src/SMAPI/Framework/Models/ModStatus.cs b/src/SMAPI/Framework/ModData/ModStatus.cs index 343ccb7e..0e1d94d4 100644 --- a/src/SMAPI/Framework/Models/ModStatus.cs +++ b/src/SMAPI/Framework/ModData/ModStatus.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Framework.ModData { /// <summary>Indicates how SMAPI should treat a mod.</summary> internal enum ModStatus diff --git a/src/SMAPI/Framework/Models/ParsedModDataRecord.cs b/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs index 0abc7b89..5a6561a7 100644 --- a/src/SMAPI/Framework/Models/ParsedModDataRecord.cs +++ b/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Framework.ModData { /// <summary>A parsed representation of the fields from a <see cref="ModDataRecord"/> for a specific manifest.</summary> internal class ParsedModDataRecord @@ -9,6 +9,9 @@ namespace StardewModdingAPI.Framework.Models /// <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; } diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 1a71920e..29bb6617 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,4 +1,4 @@ -using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Framework.ModData; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 6671e880..09a9299e 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Serialisation; @@ -17,12 +18,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Get manifest metadata for each folder in the given root path.</summary> /// <param name="rootPath">The root path to search for mods.</param> /// <param name="jsonHelper">The JSON helper with which to read manifests.</param> - /// <param name="dataRecords">Metadata about mods from SMAPI's internal data.</param> + /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <returns>Returns the manifests by relative folder.</returns> - public IEnumerable<IModMetadata> ReadManifests(string rootPath, JsonHelper jsonHelper, IEnumerable<ModDataRecord> dataRecords) + public IEnumerable<IModMetadata> ReadManifests(string rootPath, JsonHelper jsonHelper, ModDatabase modDatabase) { - dataRecords = dataRecords.ToArray(); - foreach (DirectoryInfo modDir in this.GetModFolders(rootPath)) { // read file @@ -54,22 +53,19 @@ namespace StardewModdingAPI.Framework.ModLoading } // parse internal data record (if any) - ParsedModDataRecord dataRecord = null; - if (manifest != null) - { - ModDataRecord rawDataRecord = dataRecords.FirstOrDefault(p => p.Matches(manifest)); - if (rawDataRecord != null) - dataRecord = rawDataRecord.ParseFieldsFor(manifest); - } + ParsedModDataRecord dataRecord = modDatabase.GetParsed(manifest); + + // get display name + string displayName = manifest?.Name; + if (string.IsNullOrWhiteSpace(displayName)) + displayName = dataRecord?.DisplayName; + if (string.IsNullOrWhiteSpace(displayName)) + displayName = modDir.FullName.Replace(rootPath, "").Trim('/', '\\'); // build metadata - string displayName = !string.IsNullOrWhiteSpace(manifest?.Name) - ? manifest.Name - : modDir.FullName.Replace(rootPath, "").Trim('/', '\\'); ModMetadataStatus status = error == null ? ModMetadataStatus.Found : ModMetadataStatus.Failed; - yield return new ModMetadata(displayName, modDir.FullName, manifest, dataRecord).SetStatus(status, error); } } @@ -193,7 +189,8 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Sort the given mods by the order they should be loaded.</summary> /// <param name="mods">The mods to process.</param> - public IEnumerable<IModMetadata> ProcessDependencies(IEnumerable<IModMetadata> mods) + /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> + public IEnumerable<IModMetadata> ProcessDependencies(IEnumerable<IModMetadata> mods, ModDatabase modDatabase) { // initialise metadata mods = mods.ToArray(); @@ -209,7 +206,7 @@ namespace StardewModdingAPI.Framework.ModLoading // sort mods foreach (IModMetadata mod in mods) - this.ProcessDependencies(mods.ToArray(), mod, states, sortedMods, new List<IModMetadata>()); + this.ProcessDependencies(mods.ToArray(), modDatabase, mod, states, sortedMods, new List<IModMetadata>()); return sortedMods.Reverse(); } @@ -220,12 +217,13 @@ namespace StardewModdingAPI.Framework.ModLoading *********/ /// <summary>Sort a mod's dependencies by the order they should be loaded, and remove any mods that can't be loaded due to missing or conflicting dependencies.</summary> /// <param name="mods">The full list of mods being validated.</param> + /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <param name="mod">The mod whose dependencies to process.</param> /// <param name="states">The dependency state for each mod.</param> /// <param name="sortedMods">The list in which to save mods sorted by dependency order.</param> /// <param name="currentChain">The current change of mod dependencies.</param> /// <returns>Returns the mod dependency status.</returns> - private ModDependencyStatus ProcessDependencies(IModMetadata[] mods, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain) + private ModDependencyStatus ProcessDependencies(IModMetadata[] mods, ModDatabase modDatabase, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain) { // check if already visited switch (states[mod]) @@ -276,11 +274,17 @@ namespace StardewModdingAPI.Framework.ModLoading // missing required dependencies, mark failed { - string[] failedIDs = (from entry in dependencies where entry.IsRequired && entry.Mod == null select entry.ID).ToArray(); - if (failedIDs.Any()) + string[] failedModNames = ( + from entry in dependencies + where entry.IsRequired && entry.Mod == null + let displayName = modDatabase.GetDisplayNameFor(entry.ID) ?? entry.ID + orderby displayName + select displayName + ).ToArray(); + if (failedModNames.Any()) { sortedMods.Push(mod); - mod.SetStatus(ModMetadataStatus.Failed, $"it requires mods which aren't installed ({string.Join(", ", failedIDs)})."); + mod.SetStatus(ModMetadataStatus.Failed, $"it requires mods which aren't installed ({string.Join(", ", failedModNames)})."); return states[mod] = ModDependencyStatus.Failed; } } @@ -325,7 +329,7 @@ namespace StardewModdingAPI.Framework.ModLoading } // recursively process each dependency - var substatus = this.ProcessDependencies(mods, requiredMod, states, sortedMods, subchain); + var substatus = this.ProcessDependencies(mods, modDatabase, requiredMod, states, sortedMods, subchain); switch (substatus) { // sorted successfully diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 401e1a3a..17169714 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using StardewModdingAPI.Framework.ModData; + namespace StardewModdingAPI.Framework.Models { /// <summary>The SMAPI configuration settings.</summary> @@ -22,6 +25,6 @@ namespace StardewModdingAPI.Framework.Models public bool VerboseLogging { get; set; } /// <summary>Extra metadata about mods.</summary> - public ModDataRecord[] ModData { get; set; } + public IDictionary<string, ModDataRecord> ModData { get; set; } } } |