summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ContentPack.cs
blob: 9503a0e6640699395c9c456a03fe5ce2c4e25226 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System;
using System.IO;
using StardewModdingAPI.Framework.ModHelpers;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Toolkit.Utilities.PathLookups;

namespace StardewModdingAPI.Framework
{
    /// <summary>Manages access to a content pack's metadata and files.</summary>
    internal class ContentPack : IContentPack
    {
        /*********
        ** Fields
        *********/
        /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
        private readonly JsonHelper JsonHelper;

        /// <summary>A lookup for relative paths within the <see cref="DirectoryPath"/>.</summary>
        private readonly IFilePathLookup RelativePathCache;


        /*********
        ** Accessors
        *********/
        /// <inheritdoc />
        public string DirectoryPath { get; }

        /// <inheritdoc />
        public IManifest Manifest { get; }

        /// <inheritdoc />
        public ITranslationHelper Translation => this.TranslationImpl;

        /// <inheritdoc />
        public IModContentHelper ModContent { get; }

        /// <summary>The underlying translation helper.</summary>
        internal TranslationHelper TranslationImpl { get; set; }


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="directoryPath">The full path to the content pack's folder.</param>
        /// <param name="manifest">The content pack's manifest.</param>
        /// <param name="content">Provides an API for loading content assets from the content pack's folder.</param>
        /// <param name="translation">Provides translations stored in the content pack's <c>i18n</c> folder.</param>
        /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
        /// <param name="relativePathCache">A lookup for relative paths within the <paramref name="directoryPath"/>.</param>
        public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, IFilePathLookup relativePathCache)
        {
            this.DirectoryPath = directoryPath;
            this.Manifest = manifest;
            this.ModContent = content;
            this.TranslationImpl = translation;
            this.JsonHelper = jsonHelper;
            this.RelativePathCache = relativePathCache;
        }

        /// <inheritdoc />
        public bool HasFile(string path)
        {
            path = PathUtilities.NormalizePath(path);

            return this.GetFile(path).Exists;
        }

        /// <inheritdoc />
        public TModel? ReadJsonFile<TModel>(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;
        }

        /// <inheritdoc />
        public void WriteJsonFile<TModel>(string path, TModel data) where TModel : class
        {
            path = PathUtilities.NormalizePath(path);

            FileInfo file = this.GetFile(path, out path);
            this.JsonHelper.WriteJsonFile(file.FullName, data);

            this.RelativePathCache.Add(path);
        }

        /// <inheritdoc />
        [Obsolete]
        public T LoadAsset<T>(string key)
            where T : notnull
        {
            return this.ModContent.Load<T>(key);
        }

        /// <inheritdoc />
        [Obsolete]
        public string GetActualAssetKey(string key)
        {
            return this.ModContent.GetInternalAssetName(key).Name;
        }


        /*********
        ** Private methods
        *********/
        /// <summary>Get the underlying file info.</summary>
        /// <param name="relativePath">The normalized file path relative to the content pack directory.</param>
        private FileInfo GetFile(string relativePath)
        {
            return this.GetFile(relativePath, out _);
        }

        /// <summary>Get the underlying file info.</summary>
        /// <param name="relativePath">The normalized file path relative to the content pack directory.</param>
        /// <param name="actualRelativePath">The relative path after case-insensitive matching.</param>
        private FileInfo GetFile(string relativePath, out string actualRelativePath)
        {
            if (!PathUtilities.IsSafeRelativePath(relativePath))
                throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path.");

            actualRelativePath = this.RelativePathCache.GetFilePath(relativePath);

            return new FileInfo(Path.Combine(this.DirectoryPath, actualRelativePath));
        }
    }
}