move basic mod scanning into the toolkit (#532)

This commit is contained in:
Jesse Plamondon-Willard 2018-06-30 21:00:45 -04:00
parent 34b0fd2870
commit c12777ad53
6 changed files with 200 additions and 43 deletions

View File

@ -7,8 +7,8 @@ using Newtonsoft.Json;
using NUnit.Framework;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.Tests.Core
@ -31,7 +31,7 @@ namespace StardewModdingAPI.Tests.Core
Directory.CreateDirectory(rootFolder);
// act
IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModDatabase()).ToArray();
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
// assert
Assert.AreEqual(0, mods.Length, 0, $"Expected to find zero manifests, found {mods.Length} instead.");
@ -46,7 +46,7 @@ namespace StardewModdingAPI.Tests.Core
Directory.CreateDirectory(modFolder);
// act
IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModDatabase()).ToArray();
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
IModMetadata mod = mods.FirstOrDefault();
// assert
@ -85,7 +85,7 @@ namespace StardewModdingAPI.Tests.Core
File.WriteAllText(filename, JsonConvert.SerializeObject(original));
// act
IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModDatabase()).ToArray();
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
IModMetadata mod = mods.FirstOrDefault();
// assert

View File

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Framework.ModScanning;
using StardewModdingAPI.Toolkit.Serialisation.Models;
using StardewModdingAPI.Toolkit.Utilities;
@ -17,38 +18,15 @@ namespace StardewModdingAPI.Framework.ModLoading
** Public methods
*********/
/// <summary>Get manifest metadata for each folder in the given root path.</summary>
/// <param name="toolkit">The mod toolkit.</param>
/// <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="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, ModDatabase modDatabase)
public IEnumerable<IModMetadata> ReadManifests(ModToolkit toolkit, string rootPath, ModDatabase modDatabase)
{
foreach (DirectoryInfo modDir in this.GetModFolders(rootPath))
foreach (ModFolder folder in toolkit.GetModFolders(rootPath))
{
// read file
Manifest manifest = null;
string error = null;
{
string path = Path.Combine(modDir.FullName, "manifest.json");
try
{
manifest = jsonHelper.ReadJsonFile<Manifest>(path);
if (manifest == null)
{
error = File.Exists(path)
? "its manifest is invalid."
: "it doesn't have a manifest.";
}
}
catch (SParseException ex)
{
error = $"parsing its manifest failed: {ex.Message}";
}
catch (Exception ex)
{
error = $"parsing its manifest failed:\n{ex.GetLogSummary()}";
}
}
Manifest manifest = folder.Manifest;
// parse internal data record (if any)
ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest);
@ -58,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading
if (string.IsNullOrWhiteSpace(displayName))
displayName = dataRecord?.DisplayName;
if (string.IsNullOrWhiteSpace(displayName))
displayName = PathUtilities.GetRelativePath(rootPath, modDir.FullName);
displayName = PathUtilities.GetRelativePath(rootPath, folder.ActualDirectory?.FullName ?? folder.SearchDirectory.FullName);
// apply defaults
if (manifest != null && dataRecord != null)
@ -68,10 +46,10 @@ namespace StardewModdingAPI.Framework.ModLoading
}
// build metadata
ModMetadataStatus status = error == null
ModMetadataStatus status = folder.ManifestParseError == null
? ModMetadataStatus.Found
: ModMetadataStatus.Failed;
yield return new ModMetadata(displayName, modDir.FullName, manifest, dataRecord).SetStatus(status, error);
yield return new ModMetadata(displayName, folder.ActualDirectory?.FullName, manifest, dataRecord).SetStatus(status, folder.ManifestParseError);
}
}

View File

@ -104,8 +104,8 @@ namespace StardewModdingAPI
new Regex(@"^DebugOutput: (?:added CLOUD|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant)
};
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
private readonly JsonHelper JsonHelper = new JsonHelper();
/// <summary>The mod toolkit used for generic mod interactions.</summary>
private readonly ModToolkit Toolkit = new ModToolkit();
/*********
@ -205,7 +205,7 @@ namespace StardewModdingAPI
new RectangleConverter()
};
foreach (JsonConverter converter in converters)
this.JsonHelper.JsonSettings.Converters.Add(converter);
this.Toolkit.JsonHelper.JsonSettings.Converters.Add(converter);
// add error handlers
#if SMAPI_FOR_WINDOWS
@ -423,14 +423,14 @@ namespace StardewModdingAPI
ModResolver resolver = new ModResolver();
// load manifests
IModMetadata[] mods = resolver.ReadManifests(Constants.ModPath, this.JsonHelper, modDatabase).ToArray();
IModMetadata[] mods = resolver.ReadManifests(toolkit, Constants.ModPath, modDatabase).ToArray();
resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl);
// process dependencies
mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
// load mods
this.LoadMods(mods, this.JsonHelper, this.ContentCore, modDatabase);
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
// write metadata file
if (this.Settings.DumpMetadata)
@ -443,7 +443,7 @@ namespace StardewModdingAPI
ModFolderPath = Constants.ModPath,
Mods = mods
};
this.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.metadata-dump.json"), export);
this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.metadata-dump.json"), export);
}
// check for updates
@ -875,7 +875,7 @@ namespace StardewModdingAPI
{
IMonitor packMonitor = this.GetSecondaryMonitor(packManifest.Name);
IContentHelper packContentHelper = new ContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, packMonitor);
return new ContentPack(packDirPath, packManifest, packContentHelper, this.JsonHelper);
return new ContentPack(packDirPath, packManifest, packContentHelper, this.Toolkit.JsonHelper);
}
modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, this.GameInstance.Input, events, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, contentPacks, CreateTransitionalContentPack, this.DeprecationManager);
@ -1117,7 +1117,7 @@ namespace StardewModdingAPI
/// <param name="mods">The mods for which to reload translations.</param>
private void ReloadTranslations(IEnumerable<IModMetadata> mods)
{
JsonHelper jsonHelper = this.JsonHelper;
JsonHelper jsonHelper = this.Toolkit.JsonHelper;
foreach (IModMetadata metadata in mods)
{
if (metadata.IsContentPack)

View File

@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
{
/// <summary>The info about a mod read from its folder.</summary>
public class ModFolder
{
/*********
** Accessors
*********/
/// <summary>The Mods subfolder containing this mod.</summary>
public DirectoryInfo SearchDirectory { get; }
/// <summary>The folder containing manifest.json.</summary>
public DirectoryInfo ActualDirectory { 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; }
/*********
** Public methods
*********/
/// <summary>Construct an instance when a mod wasn't found in a folder.</summary>
/// <param name="searchDirectory">The directory that was searched.</param>
public ModFolder(DirectoryInfo searchDirectory)
{
this.SearchDirectory = searchDirectory;
}
/// <summary>Construct an instance.</summary>
/// <param name="searchDirectory">The Mods subfolder containing this mod.</param>
/// <param name="actualDirectory">The folder containing manifest.json.</param>
/// <param name="manifest">The mod manifest.</param>
/// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param>
public ModFolder(DirectoryInfo searchDirectory, DirectoryInfo actualDirectory, Manifest manifest, string manifestParseError = null)
{
this.SearchDirectory = searchDirectory;
this.ActualDirectory = actualDirectory;
this.Manifest = manifest;
this.ManifestParseError = manifestParseError;
}
/// <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();
}
}
}

View File

@ -0,0 +1,103 @@
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
{
/*********
** Properties
*********/
/// <summary>The JSON helper with which to read manifests.</summary>
private readonly JsonHelper JsonHelper;
/*********
** 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)
{
foreach (DirectoryInfo folder in new DirectoryInfo(rootPath).EnumerateDirectories())
yield return this.ReadFolder(rootPath, folder);
}
/// <summary>Extract information from a mod folder.</summary>
/// <param name="rootPath">The root folder containing mods.</param>
/// <param name="searchFolder">The folder to search for a mod.</param>
public ModFolder ReadFolder(string rootPath, DirectoryInfo searchFolder)
{
// find manifest.json
FileInfo manifestFile = this.FindManifest(searchFolder);
if (manifestFile == null)
return new ModFolder(searchFolder);
// read mod info
Manifest manifest = null;
string manifestError = null;
{
try
{
manifest = this.JsonHelper.ReadJsonFile<Manifest>(manifestFile.FullName);
if (manifest == null)
{
manifestError = File.Exists(manifestFile.FullName)
? "its manifest is invalid."
: "it doesn't have a manifest.";
}
}
catch (SParseException ex)
{
manifestError = $"parsing its manifest failed: {ex.Message}";
}
catch (Exception ex)
{
manifestError = $"parsing its manifest failed:\n{ex}";
}
}
return new ModFolder(searchFolder, manifestFile.Directory, manifest, manifestError);
}
/*********
** Private methods
*********/
/// <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;
}
}
}
}

View File

@ -6,6 +6,8 @@ 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
{
@ -27,6 +29,13 @@ namespace StardewModdingAPI.Toolkit
};
/*********
** Accessors
*********/
/// <summary>Encapsulates SMAPI's JSON parsing.</summary>
public JsonHelper JsonHelper { get; } = new JsonHelper();
/*********
** Public methods
*********/
@ -53,6 +62,13 @@ namespace StardewModdingAPI.Toolkit
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)