using System; using System.IO; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StardewModdingAPI.Framework; namespace StardewModdingAPI { /// <summary>A dynamic configuration class for a mod.</summary> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public abstract class Config { /********* ** Properties *********/ /// <summary>Manages deprecation warnings.</summary> private static DeprecationManager DeprecationManager; /********* ** 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>Injects types required for backwards compatibility.</summary> /// <param name="deprecationManager">Manages deprecation warnings.</param> internal static void Shim(DeprecationManager deprecationManager) { Config.DeprecationManager = deprecationManager; } /// <summary>Construct an instance of the config class.</summary> /// <typeparam name="T">The config class type.</typeparam> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] 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> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public virtual T LoadConfig<T>() 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<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.Error($"Invalid JSON ({this.GetType().Name}): {this.ConfigLocation} \n{ex}"); return this.GenerateDefaultConfig<T>(); } } returnValue.WriteConfig(); return returnValue; } /// <summary>Get the default config values.</summary> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public virtual T GenerateDefaultConfig<T>() where T : Config { return null; } /// <summary>Get the current configuration with missing values defaulted.</summary> /// <typeparam name="T">The config class type.</typeparam> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] 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.Error($"An error occured when updating a config: {ex}"); return this as T; } } /********* ** Protected methods *********/ /// <summary>Construct an instance.</summary> protected Config() { Config.DeprecationManager.Warn("the Config class", "1.0", DeprecationLevel.Info); Config.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings } } /// <summary>Provides extension methods for <see cref="Config"/> classes.</summary> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] 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> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] 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.Error("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> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public static void WriteConfig<T>(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); } /// <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> [Obsolete("This base class is obsolete since SMAPI 1.0. See the latest project README for details.")] public static T ReloadConfig<T>(this T baseConfig) where T : Config { return baseConfig.LoadConfig<T>(); } } }