summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Config.cs
blob: ff8ade907b85fa01c4163ccdf70ca5642015b8ec (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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace StardewModdingAPI
{
    /// <summary>A dynamic configuration class for a mod.</summary>
    public abstract class Config
    {
        /*********
        ** Accessors
        *********/
        /// <summary>The full path to the configuration file.</summary>
        [JsonIgnore]
        public virtual string ConfigLocation { get; protected internal set; }

        /// <summary>The directory path containing the configuration file.</summary>
        [JsonIgnore]
        public virtual string ConfigDir => Path.GetDirectoryName(this.ConfigLocation);


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance of the config class.</summary>
        /// <typeparam name="T">The config class type.</typeparam>
        public virtual Config Instance<T>() where T : Config => Activator.CreateInstance<T>();

        /// <summary>Load the config from the JSON file, saving it to disk if needed.</summary>
        /// <typeparam name="T">The config class type.</typeparam>
        public virtual T LoadConfig<T>() where T : Config
        {
            // validate
            if (string.IsNullOrEmpty(this.ConfigLocation))
            {
                Log.AsyncR("A config tried to load without specifying a location on the disk.");
                return null;
            }

            // read or generate config
            T returnValue;
            if (!File.Exists(this.ConfigLocation))
            {
                T config = this.GenerateDefaultConfig<T>();
                config.ConfigLocation = this.ConfigLocation;
                returnValue = config;
            }
            else
            {
                try
                {
                    T config = JsonConvert.DeserializeObject<T>(File.ReadAllText(this.ConfigLocation));
                    config.ConfigLocation = this.ConfigLocation;
                    returnValue = config.UpdateConfig<T>();
                }
                catch (Exception ex)
                {
                    Log.AsyncR($"Invalid JSON ({this.GetType().Name}): {this.ConfigLocation} \n{ex}");
                    return this.GenerateDefaultConfig<T>();
                }
            }

            returnValue.WriteConfig();
            return returnValue;
        }

        /// <summary>Get the default config values.</summary>
        public abstract T GenerateDefaultConfig<T>() where T : Config;

        /// <summary>Get the current configuration with missing values defaulted.</summary>
        /// <typeparam name="T">The config class type.</typeparam>
        public virtual T UpdateConfig<T>() where T : Config
        {
            try
            {
                // get default + user config
                JObject defaultConfig = JObject.FromObject(this.Instance<T>().GenerateDefaultConfig<T>());
                JObject currentConfig = JObject.FromObject(this);
                defaultConfig.Merge(currentConfig, new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Replace });

                // cast json object to config
                T config = defaultConfig.ToObject<T>();

                // update location
                config.ConfigLocation = this.ConfigLocation;

                return config;
            }
            catch (Exception ex)
            {
                Log.AsyncR($"An error occured when updating a config: {ex}");
                return this as T;
            }
        }
    }

    /// <summary>Provides extension methods for <see cref="Config"/> classes.</summary>
    public static class ConfigExtensions
    {
        /// <summary>Initialise the configuration. That includes loading, saving, and merging the config file and in memory at a default state. This method should not be used to reload or to resave a config. NOTE: You MUST set your config EQUAL to the return of this method!</summary>
        /// <typeparam name="T">The config class type.</typeparam>
        /// <param name="baseConfig">The base configuration to initialise.</param>
        /// <param name="configLocation">The base configuration file path.</param>
        public static T InitializeConfig<T>(this T baseConfig, string configLocation) where T : Config
        {
            if (baseConfig == null)
                baseConfig = Activator.CreateInstance<T>();

            if (string.IsNullOrEmpty(configLocation))
            {
                Log.AsyncR("A config tried to initialize without specifying a location on the disk.");
                return null;
            }

            baseConfig.ConfigLocation = configLocation;
            return baseConfig.LoadConfig<T>();
        }

        /// <summary>Writes the configuration to the JSON file.</summary>
        /// <typeparam name="T">The config class type.</typeparam>
        /// <param name="baseConfig">The base configuration to initialise.</param>
        public static void WriteConfig<T>(this T baseConfig) where T : Config
        {
            if (string.IsNullOrEmpty(baseConfig?.ConfigLocation) || string.IsNullOrEmpty(baseConfig.ConfigDir))
            {
                Log.AsyncR("A config attempted to save when it itself or it's location were null.");
                return;
            }

            string json = JsonConvert.SerializeObject(baseConfig, Formatting.Indented);
            if (!Directory.Exists(baseConfig.ConfigDir))
                Directory.CreateDirectory(baseConfig.ConfigDir);

            if (!File.Exists(baseConfig.ConfigLocation) || !File.ReadAllText(baseConfig.ConfigLocation).SequenceEqual(json))
                File.WriteAllText(baseConfig.ConfigLocation, json);
        }

        /// <summary>Rereads the JSON file and merges its values with a default config. NOTE: You MUST set your config EQUAL to the return of this method!</summary>
        /// <typeparam name="T">The config class type.</typeparam>
        /// <param name="baseConfig">The base configuration to initialise.</param>
        public static T ReloadConfig<T>(this T baseConfig) where T : Config
        {
            return baseConfig.LoadConfig<T>();
        }
    }
}