using System; using System.IO; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StardewModdingAPI.Framework; namespace StardewModdingAPI { /// A dynamic configuration class for a mod. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public abstract class Config { /********* ** Accessors *********/ /// The full path to the configuration file. [JsonIgnore] public virtual string ConfigLocation { get; protected internal set; } /// The directory path containing the configuration file. [JsonIgnore] public virtual string ConfigDir => Path.GetDirectoryName(this.ConfigLocation); /********* ** Public methods *********/ /// Construct an instance of the config class. /// The config class type. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public virtual Config Instance() where T : Config => Activator.CreateInstance(); /// Load the config from the JSON file, saving it to disk if needed. /// The config class type. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public virtual T LoadConfig() where T : Config { // validate if (string.IsNullOrEmpty(this.ConfigLocation)) { Log.Error("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(); config.ConfigLocation = this.ConfigLocation; returnValue = config; } else { try { T config = JsonConvert.DeserializeObject(File.ReadAllText(this.ConfigLocation)); config.ConfigLocation = this.ConfigLocation; returnValue = config.UpdateConfig(); } catch (Exception ex) { Log.Error($"Invalid JSON ({this.GetType().Name}): {this.ConfigLocation} \n{ex}"); return this.GenerateDefaultConfig(); } } returnValue.WriteConfig(); return returnValue; } /// Get the default config values. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public virtual T GenerateDefaultConfig() where T : Config { return null; } /// Get the current configuration with missing values defaulted. /// The config class type. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public virtual T UpdateConfig() where T : Config { try { // get default + user config JObject defaultConfig = JObject.FromObject(this.Instance().GenerateDefaultConfig()); JObject currentConfig = JObject.FromObject(this); defaultConfig.Merge(currentConfig, new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Replace }); // cast json object to config T config = defaultConfig.ToObject(); // update location config.ConfigLocation = this.ConfigLocation; return config; } catch (Exception ex) { Log.Error($"An error occured when updating a config: {ex}"); return this as T; } } /********* ** Protected methods *********/ /// Construct an instance. protected Config() { Program.DeprecationManager.Warn("the Config class", "1.0", DeprecationLevel.Notice); Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings } } /// Provides extension methods for classes. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public static class ConfigExtensions { /// 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! /// The config class type. /// The base configuration to initialise. /// The base configuration file path. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public static T InitializeConfig(this T baseConfig, string configLocation) where T : Config { if (baseConfig == null) baseConfig = Activator.CreateInstance(); if (string.IsNullOrEmpty(configLocation)) { Log.Error("A config tried to initialize without specifying a location on the disk."); return null; } baseConfig.ConfigLocation = configLocation; return baseConfig.LoadConfig(); } /// Writes the configuration to the JSON file. /// The config class type. /// The base configuration to initialise. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public static void WriteConfig(this T baseConfig) where T : Config { if (string.IsNullOrEmpty(baseConfig?.ConfigLocation) || string.IsNullOrEmpty(baseConfig.ConfigDir)) { Log.Error("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); } /// 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! /// The config class type. /// The base configuration to initialise. [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public static T ReloadConfig(this T baseConfig) where T : Config { return baseConfig.LoadConfig(); } } }