using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace BepInEx.Configuration
{
///
/// A helper class to handle persistent data.
///
public class ConfigFile
{
private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
internal static ConfigFile CoreConfig { get; } = new ConfigFile(Paths.BepInExConfigPath, true);
protected internal Dictionary Cache { get; } = new Dictionary();
public ReadOnlyCollection ConfigDefinitions => Cache.Keys.ToList().AsReadOnly();
///
/// An event that is fired every time the config is reloaded.
///
public event EventHandler ConfigReloaded;
public string ConfigFilePath { get; }
///
/// If enabled, writes the config to disk every time a value is set.
///
public bool SaveOnConfigSet { get; set; } = true;
public ConfigFile(string configPath, bool saveOnInit)
{
ConfigFilePath = configPath;
if (File.Exists(ConfigFilePath))
{
Reload();
}
else if (saveOnInit)
{
Save();
}
}
private object _ioLock = new object();
///
/// Reloads the config from disk. Unsaved changes are lost.
///
public void Reload()
{
lock (_ioLock)
{
Dictionary descriptions = Cache.ToDictionary(x => x.Key, x => x.Key.Description);
string currentSection = "";
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);
if (descriptions.ContainsKey(definition))
definition.Description = descriptions[definition];
Cache[definition] = currentValue;
}
ConfigReloaded?.Invoke(this, EventArgs.Empty);
}
}
///
/// Writes the config to disk.
///
public void Save()
{
lock (_ioLock)
{
if (!Directory.Exists(Paths.ConfigPath))
Directory.CreateDirectory(Paths.ConfigPath);
using (StreamWriter writer = new StreamWriter(File.Create(ConfigFilePath), System.Text.Encoding.UTF8))
foreach (var sectionKv in Cache.GroupBy(x => x.Key.Section).OrderBy(x => x.Key))
{
writer.WriteLine($"[{sectionKv.Key}]");
foreach (var entryKv in sectionKv)
{
writer.WriteLine();
if (!string.IsNullOrEmpty(entryKv.Key.Description))
writer.WriteLine($"# {entryKv.Key.Description.Replace("\n", "\n# ")}");
writer.WriteLine($"{entryKv.Key.Key} = {entryKv.Value}");
}
writer.WriteLine();
}
}
}
public ConfigWrapper Wrap(ConfigDefinition configDefinition, T defaultValue = default(T))
{
if (!Cache.ContainsKey(configDefinition))
{
Cache.Add(configDefinition, TomlTypeConverter.ConvertToString(defaultValue));
Save();
}
else
{
var original = Cache.Keys.First(x => x.Equals(configDefinition));
if (original.Description != configDefinition.Description)
{
original.Description = configDefinition.Description;
Save();
}
}
return new ConfigWrapper(this, configDefinition);
}
public ConfigWrapper Wrap(string section, string key, string description = null, T defaultValue = default(T))
=> Wrap(new ConfigDefinition(section, key, description), defaultValue);
}
}