using System;
using System.Collections.Generic;
using System.IO;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Framework
{
/// Manages access to a content pack's metadata and files.
internal class ContentPack : IContentPack
{
/*********
** Fields
*********/
/// Provides an API for loading content assets.
private readonly IContentHelper Content;
/// Encapsulates SMAPI's JSON file parsing.
private readonly JsonHelper JsonHelper;
/// A cache of case-insensitive => exact relative paths within the content pack, for case-insensitive file lookups on Linux/macOS.
private readonly IDictionary RelativePaths = new Dictionary(StringComparer.OrdinalIgnoreCase);
/*********
** Accessors
*********/
///
public string DirectoryPath { get; }
///
public IManifest Manifest { get; }
///
public ITranslationHelper Translation { get; }
/*********
** Public methods
*********/
/// Construct an instance.
/// The full path to the content pack's folder.
/// The content pack's manifest.
/// Provides an API for loading content assets.
/// Provides translations stored in the content pack's i18n folder.
/// Encapsulates SMAPI's JSON file parsing.
public ContentPack(string directoryPath, IManifest manifest, IContentHelper content, ITranslationHelper translation, JsonHelper jsonHelper)
{
this.DirectoryPath = directoryPath;
this.Manifest = manifest;
this.Content = content;
this.Translation = translation;
this.JsonHelper = jsonHelper;
foreach (string path in Directory.EnumerateFiles(this.DirectoryPath, "*", SearchOption.AllDirectories))
{
string relativePath = path.Substring(this.DirectoryPath.Length + 1);
this.RelativePaths[relativePath] = relativePath;
}
}
///
public bool HasFile(string path)
{
path = PathUtilities.NormalizePath(path);
return this.GetFile(path).Exists;
}
///
public TModel ReadJsonFile(string path) where TModel : class
{
path = PathUtilities.NormalizePath(path);
FileInfo file = this.GetFile(path);
return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel model)
? model
: null;
}
///
public void WriteJsonFile(string path, TModel data) where TModel : class
{
path = PathUtilities.NormalizePath(path);
FileInfo file = this.GetFile(path, out path);
this.JsonHelper.WriteJsonFile(file.FullName, data);
if (!this.RelativePaths.ContainsKey(path))
this.RelativePaths[path] = path;
}
///
public T LoadAsset(string key)
{
key = PathUtilities.NormalizePath(key);
key = this.GetCaseInsensitiveRelativePath(key);
return this.Content.Load(key, ContentSource.ModFolder);
}
///
public string GetActualAssetKey(string key)
{
key = PathUtilities.NormalizePath(key);
key = this.GetCaseInsensitiveRelativePath(key);
return this.Content.GetActualAssetKey(key, ContentSource.ModFolder);
}
/*********
** Private methods
*********/
/// Get the real relative path from a case-insensitive path.
/// The normalized relative path.
private string GetCaseInsensitiveRelativePath(string relativePath)
{
if (!PathUtilities.IsSafeRelativePath(relativePath))
throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path.");
return !string.IsNullOrWhiteSpace(relativePath) && this.RelativePaths.TryGetValue(relativePath, out string caseInsensitivePath)
? caseInsensitivePath
: relativePath;
}
/// Get the underlying file info.
/// The normalized file path relative to the content pack directory.
private FileInfo GetFile(string relativePath)
{
return this.GetFile(relativePath, out _);
}
/// Get the underlying file info.
/// The normalized file path relative to the content pack directory.
/// The relative path after case-insensitive matching.
private FileInfo GetFile(string relativePath, out string actualRelativePath)
{
actualRelativePath = this.GetCaseInsensitiveRelativePath(relativePath);
return new FileInfo(Path.Combine(this.DirectoryPath, actualRelativePath));
}
}
}