| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 | 
							- using System;
 
- using System.Collections.Generic;
 
- using System.Collections.ObjectModel;
 
- using System.IO;
 
- using System.Linq;
 
- using System.Text;
 
- using BepInEx.Logging;
 
- namespace BepInEx.Configuration
 
- {
 
- 	/// <summary>
 
- 	/// A helper class to handle persistent data.
 
- 	/// </summary>
 
- 	public class ConfigFile
 
- 	{
 
- 		private readonly BepInPlugin _ownerMetadata;
 
- 		internal static ConfigFile CoreConfig { get; } = new ConfigFile(Paths.BepInExConfigPath, true);
 
- 		/// <summary>
 
- 		/// All config entries inside 
 
- 		/// </summary>
 
- 		protected Dictionary<ConfigDefinition, ConfigEntry> Entries { get; } = new Dictionary<ConfigDefinition, ConfigEntry>();
 
- 		/// <summary>
 
- 		/// Create a list with all config entries inside of this config file.
 
- 		/// </summary>
 
- 		[Obsolete("Use GetConfigEntries instead")]
 
- 		public ReadOnlyCollection<ConfigDefinition> ConfigDefinitions => Entries.Keys.ToList().AsReadOnly();
 
- 		/// <summary>
 
- 		/// Create an array with all config entries inside of this config file. Should be only used for metadata purposes.
 
- 		/// If you want to access and modify an existing setting then use <see cref="Wrap{T}(ConfigDefinition,T,ConfigDescription)"/> 
 
- 		/// instead with no description.
 
- 		/// </summary>
 
- 		public ConfigEntry[] GetConfigEntries() => Entries.Values.ToArray();
 
- 		/// <summary>
 
- 		/// Full path to the config file. The file might not exist until a setting is added and changed, or <see cref="Save"/> is called.
 
- 		/// </summary>
 
- 		public string ConfigFilePath { get; }
 
- 		/// <summary>
 
- 		/// If enabled, writes the config to disk every time a value is set. 
 
- 		/// If disabled, you have to manually use <see cref="Save"/> or the changes will be lost!
 
- 		/// </summary>
 
- 		public bool SaveOnConfigSet { get; set; } = true;
 
- 		/// <summary>
 
- 		/// Create a new config file at the specified config path.
 
- 		/// </summary>
 
- 		/// <param name="configPath">Full path to a file that contains settings. The file will be created as needed.</param>
 
- 		/// <param name="saveOnInit">If the config file/directory doesn't exist, create it immediately.</param>
 
- 		/// <param name="owner">The plugin that owns this setting.</param>
 
- 		public ConfigFile(string configPath, bool saveOnInit, BaseUnityPlugin owner = null)
 
- 		{
 
- 			_ownerMetadata = owner?.Info.Metadata;
 
- 			if (configPath == null) throw new ArgumentNullException(nameof(configPath));
 
- 			configPath = Path.GetFullPath(configPath);
 
- 			ConfigFilePath = configPath;
 
- 			if (File.Exists(ConfigFilePath))
 
- 			{
 
- 				Reload();
 
- 			}
 
- 			else if (saveOnInit)
 
- 			{
 
- 				Save();
 
- 			}
 
- 			StartWatching();
 
- 		}
 
- 		#region Save/Load
 
- 		private readonly object _ioLock = new object();
 
- 		/// <summary>
 
- 		/// Reloads the config from disk. Unsaved changes are lost.
 
- 		/// </summary>
 
- 		public void Reload()
 
- 		{
 
- 			lock (_ioLock)
 
- 			{
 
- 				string currentSection = string.Empty;
 
- 				foreach (string rawLine in File.ReadAllLines(ConfigFilePath))
 
- 				{
 
- 					string line = rawLine.Trim();
 
- 					if (line.StartsWith("#")) //comment
 
- 						continue;
 
- 					if (line.StartsWith("[") && line.EndsWith("]")) //section
 
- 					{
 
- 						currentSection = line.Substring(1, line.Length - 2);
 
- 						continue;
 
- 					}
 
- 					string[] split = line.Split('='); //actual config line
 
- 					if (split.Length != 2)
 
- 						continue; //empty/invalid line
 
- 					string currentKey = split[0].Trim();
 
- 					string currentValue = split[1].Trim();
 
- 					var definition = new ConfigDefinition(currentSection, currentKey);
 
- 					Entries.TryGetValue(definition, out ConfigEntry entry);
 
- 					if (entry == null)
 
- 					{
 
- 						entry = new ConfigEntry(this, definition);
 
- 						Entries[definition] = entry;
 
- 					}
 
- 					entry.SetSerializedValue(currentValue, true, this);
 
- 				}
 
- 			}
 
- 			OnConfigReloaded();
 
- 		}
 
- 		/// <summary>
 
- 		/// Writes the config to disk.
 
- 		/// </summary>
 
- 		public void Save()
 
- 		{
 
- 			lock (_ioLock)
 
- 			{
 
- 				StopWatching();
 
- 				string directoryName = Path.GetDirectoryName(ConfigFilePath);
 
- 				if (directoryName != null) Directory.CreateDirectory(directoryName);
 
- 				using (var writer = new StreamWriter(File.Create(ConfigFilePath), Encoding.UTF8))
 
- 				{
 
- 					if (_ownerMetadata != null)
 
- 					{
 
- 						writer.WriteLine($"## Settings file was created by plugin {_ownerMetadata.Name} v{_ownerMetadata.Version}");
 
- 						writer.WriteLine($"## Plugin GUID: {_ownerMetadata.GUID}");
 
- 						writer.WriteLine();
 
- 					}
 
- 					foreach (var sectionKv in Entries.GroupBy(x => x.Key.Section).OrderBy(x => x.Key))
 
- 					{
 
- 						// Section heading
 
- 						writer.WriteLine($"[{sectionKv.Key}]");
 
- 						foreach (var configEntry in sectionKv.Select(x => x.Value))
 
- 						{
 
- 							writer.WriteLine();
 
- 							configEntry.WriteDescription(writer);
 
- 							writer.WriteLine($"{configEntry.Definition.Key} = {configEntry.GetSerializedValue()}");
 
- 						}
 
- 						writer.WriteLine();
 
- 					}
 
- 				}
 
- 				StartWatching();
 
- 			}
 
- 		}
 
- 		#endregion
 
- 		#region Wraps
 
- 		/// <summary>
 
- 		/// Create a new setting or access one of the existing ones. The setting is saved to drive and loaded automatically.
 
- 		/// If you are the creator of the setting, provide a ConfigDescription object to give user information about the setting.
 
- 		/// If you are using a setting created by another plugin/class, do not provide any ConfigDescription.
 
- 		/// </summary>
 
- 		/// <typeparam name="T">Type of the value contained in this setting.</typeparam>
 
- 		/// <param name="configDefinition">Section and Key of the setting.</param>
 
- 		/// <param name="defaultValue">Value of the setting if the setting was not created yet.</param>
 
- 		/// <param name="configDescription">Description of the setting shown to the user.</param>
 
- 		/// <returns></returns>
 
- 		public ConfigWrapper<T> Wrap<T>(ConfigDefinition configDefinition, T defaultValue, ConfigDescription configDescription = null)
 
- 		{
 
- 			if (!TomlTypeConverter.CanConvert(typeof(T)))
 
- 				throw new ArgumentException($"Type {typeof(T)} is not supported by the config system. Supported types: {string.Join(", ", TomlTypeConverter.GetSupportedTypes().Select(x => x.Name).ToArray())}");
 
- 			Entries.TryGetValue(configDefinition, out var entry);
 
- 			if (entry == null)
 
- 			{
 
- 				entry = new ConfigEntry(this, configDefinition, typeof(T), defaultValue);
 
- 				Entries[configDefinition] = entry;
 
- 			}
 
- 			else
 
- 			{
 
- 				entry.SetTypeAndDefaultValue(typeof(T), defaultValue, !Equals(defaultValue, default(T)));
 
- 			}
 
- 			if (configDescription != null)
 
- 			{
 
- 				if (entry.Description != null)
 
- 					Logger.Log(LogLevel.Warning, $"Tried to add configDescription to setting {configDefinition} when it already had one defined. Only add configDescription once or a random one will be used.");
 
- 				if (configDescription.AcceptableValues != null)
 
- 				{
 
- 					var genericArguments = configDescription.AcceptableValues.GetType().GetGenericArguments();
 
- 					if (genericArguments.Length > 0 && genericArguments[0] != typeof(T))
 
- 						throw new ArgumentException("AcceptableValues has a different type than the setting type", nameof(configDefinition));
 
- 				}
 
- 				entry.SetDescription(configDescription);
 
- 			}
 
- 			return new ConfigWrapper<T>(entry);
 
- 		}
 
- 		/// <summary>
 
- 		/// Create a new setting or access one of the existing ones. The setting is saved to drive and loaded automatically.
 
- 		/// If you are the creator of the setting, provide a ConfigDescription object to give user information about the setting.
 
- 		/// If you are using a setting created by another plugin/class, do not provide any ConfigDescription.
 
- 		/// </summary>
 
- 		[Obsolete("Use other Wrap overloads instead")]
 
- 		public ConfigWrapper<T> Wrap<T>(string section, string key, string description = null, T defaultValue = default(T))
 
- 			=> Wrap(new ConfigDefinition(section, key), defaultValue, string.IsNullOrEmpty(description) ? null : new ConfigDescription(description));
 
- 		/// <summary>
 
- 		/// Create a new setting or access one of the existing ones. The setting is saved to drive and loaded automatically.
 
- 		/// If you are the creator of the setting, provide a ConfigDescription object to give user information about the setting.
 
- 		/// If you are using a setting created by another plugin/class, do not provide any ConfigDescription.
 
- 		/// </summary>
 
- 		/// <typeparam name="T">Type of the value contained in this setting.</typeparam>
 
- 		/// <param name="section">Section/category/group of the setting. Settings are grouped by this.</param>
 
- 		/// <param name="key">Name of the setting.</param>
 
- 		/// <param name="defaultValue">Value of the setting if the setting was not created yet.</param>
 
- 		/// <param name="configDescription">Description of the setting shown to the user.</param>
 
- 		/// <returns></returns>
 
- 		public ConfigWrapper<T> Wrap<T>(string section, string key, T defaultValue, ConfigDescription configDescription = null)
 
- 			=> Wrap(new ConfigDefinition(section, key), defaultValue, configDescription);
 
- 		#endregion
 
- 		#region Events
 
- 		/// <summary>
 
- 		/// An event that is fired every time the config is reloaded.
 
- 		/// </summary>
 
- 		public event EventHandler ConfigReloaded;
 
- 		/// <summary>
 
- 		/// Fired when one of the settings is changed.
 
- 		/// </summary>
 
- 		public event EventHandler<SettingChangedEventArgs> SettingChanged;
 
- 		internal void OnSettingChanged(object sender, ConfigEntry changedEntry)
 
- 		{
 
- 			if (changedEntry == null) throw new ArgumentNullException(nameof(changedEntry));
 
- 			if (SettingChanged != null)
 
- 			{
 
- 				var args = new SettingChangedEventArgs(changedEntry);
 
- 				foreach (var callback in SettingChanged.GetInvocationList().Cast<EventHandler<SettingChangedEventArgs>>())
 
- 				{
 
- 					try
 
- 					{
 
- 						callback(sender, args);
 
- 					}
 
- 					catch (Exception e)
 
- 					{
 
- 						Logger.Log(LogLevel.Error, e);
 
- 					}
 
- 				}
 
- 			}
 
- 			// Check sender to prevent infinite loops
 
- 			// todo batching / async?
 
- 			if (sender != this && SaveOnConfigSet)
 
- 				Save();
 
- 		}
 
- 		private void OnConfigReloaded()
 
- 		{
 
- 			if (ConfigReloaded != null)
 
- 			{
 
- 				foreach (var callback in ConfigReloaded.GetInvocationList().Cast<EventHandler>())
 
- 				{
 
- 					try
 
- 					{
 
- 						callback(this, EventArgs.Empty);
 
- 					}
 
- 					catch (Exception e)
 
- 					{
 
- 						Logger.Log(LogLevel.Error, e);
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 		#endregion
 
- 		#region File watcher
 
- 		private FileSystemWatcher _watcher;
 
- 		/// <summary>
 
- 		/// Start watching the config file on disk for changes.
 
- 		/// </summary>
 
- 		public void StartWatching()
 
- 		{
 
- 			lock (_ioLock)
 
- 			{
 
- 				if (_watcher != null) return;
 
- 				_watcher = new FileSystemWatcher
 
- 				{
 
- 					Path = Path.GetDirectoryName(ConfigFilePath) ?? throw new ArgumentException("Invalid config path"),
 
- 					Filter = Path.GetFileName(ConfigFilePath),
 
- 					IncludeSubdirectories = false,
 
- 					NotifyFilter = NotifyFilters.LastWrite,
 
- 					EnableRaisingEvents = true
 
- 				};
 
- 				_watcher.Changed += (sender, args) => Reload();
 
- 			}
 
- 		}
 
- 		/// <summary>
 
- 		/// Stop watching the config file on disk for changes.
 
- 		/// </summary>
 
- 		public void StopWatching()
 
- 		{
 
- 			lock (_ioLock)
 
- 			{
 
- 				if (_watcher != null)
 
- 				{
 
- 					_watcher.EnableRaisingEvents = false;
 
- 					_watcher.Dispose();
 
- 					_watcher = null;
 
- 				}
 
- 			}
 
- 		}
 
- 		/// <inheritdoc />
 
- 		~ConfigFile()
 
- 		{
 
- 			StopWatching();
 
- 		}
 
- 		#endregion
 
- 	}
 
- }
 
 
  |