using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; using System.ComponentModel; using System.IO; using System.Reflection; using EasyDevCore.Common; using System.Text.Json; using System.Xml; using EasyDevCore.Common.Security; using System.Collections; using System.Runtime.CompilerServices; namespace EasyDevCore.Configuration { /// /// Easy Configuration file /// public class EasyConfiguration : INotifyPropertyChanged, IEnumerable, IDisposable { System.Timers.Timer _AutoSaveTimer; private bool _DataChanged = false; /// /// Gets or sets a value indicating whether this instance is loading file. /// /// /// true if this instance is loading file; otherwise, false. /// protected bool IsLoadingFile { get; set; } = false; private void OptionChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(Options.AutoSaveDelay): if (_AutoSaveTimer != null) { _AutoSaveTimer.Interval = Options.AutoSaveDelay; } break; case nameof(Options.AutoSave): if (_AutoSaveTimer == null) { _AutoSaveTimer = new System.Timers.Timer(Options.AutoSaveDelay * 1000); _AutoSaveTimer.Elapsed += AutoSaveElapsed; _AutoSaveTimer.Start(); } break; } } /// /// Gets the options. /// /// /// The options. /// public EasyConfigOptions Options { get; private set; } /// /// The items /// protected Dictionary Items { get; private set; } = new Dictionary(StringComparer.InvariantCultureIgnoreCase); /// /// Prevents a default instance of the class from being created. /// /// /// /// private EasyConfiguration() { LoadDefaultConfig(); } /// /// Initializes a new instance of the class. /// /// The options. public EasyConfiguration(EasyConfigOptions options) : this() { Options = options; options.PropertyChanged += OptionChanged; string configFile = Options.FileName; var ext = Path.GetExtension(Options.FileName).ToLower(); if (Options.ConfigType == EasyConfigFileType.Auto) { if (!ext.In(".xml", ".json")) { throw new ConfigurationErrorsException("Extension of file must be .xml/.json"); } Options.ConfigType = ext.Switch(new[] { ".xml", ".json" }, new[] { EasyConfigFileType.Xml, EasyConfigFileType.Json }); } else { if (string.IsNullOrWhiteSpace(ext)) { ext = Options.ConfigType.Switch(new[] { EasyConfigFileType.Xml, EasyConfigFileType.Json }, new[] { ".xml", ".json" }); configFile += ext; } } Options.FileName = configFile; } /// /// Initializes a new instance of the class. /// /// The configuration file. /// The password. public EasyConfiguration(string configFile, string password = "") : this(new() { FileName = configFile, EncryptPassword = password }) { } /// /// Loads the default config. /// private void LoadDefaultConfig() { PropertyInfo[] properties = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(p => p.CanWrite && p.CanRead).ToArray(); foreach (PropertyInfo property in properties) { EasyConfigItem item = new(); string itemName = property.Name; Items[itemName] = item; item.PropertyType = property.PropertyType; Attribute[] attrs = (Attribute[])property.GetCustomAttributes(typeof(EasyConfigPropertyAttribute), false); if (attrs.Length > 0) { EasyConfigPropertyAttribute configItem = (EasyConfigPropertyAttribute)attrs[0]; item.Property = configItem; item.Value = configItem.DefaultValue.ChangeType(property.PropertyType); } else { item.Property = new(); } } DataChanged = false; } /// /// Clears this instance. /// public void Clear() { Items.Clear(); LoadDefaultConfig(); } /// /// Gets or sets the encryption handler. /// /// /// The encryption handler (string: value, bool: true/false - Encrypted/Decrypted). /// public Func EncryptionHandler { get; set; } /// /// Gets or sets a value indicating whether [data changed]. /// /// /// true if [data changed]; otherwise, false. /// public bool DataChanged { get { return _DataChanged; } protected set { _DataChanged = value; if (_AutoSaveTimer != null) { if (DataChanged) { _AutoSaveTimer.Stop(); _AutoSaveTimer.Start(); } else { _AutoSaveTimer.Stop(); } } } } /// /// Automatics the save elapsed. /// /// The sender. /// The instance containing the event data. private void AutoSaveElapsed(object sender, System.Timers.ElapsedEventArgs e) { _AutoSaveTimer.Stop(); lock (this) { this.Save(); } } /// /// Gets or sets the with the specified name. /// /// /// The . /// /// The name (case insensitive). /// /// /// public object this[string name] { get { return Items[name].Value; } set { var item = Items[name]; if (item.Value != value) { if ((item.Property.MinValue != null && value.CompareValue(item.Property.MinValue) == 1) || (item.Property.MaxValue != null && value.CompareValue(item.Property.MaxValue) == 2)) { var s = string.Format("[{0}] must be {1}{2}{3}", name, (item.Property.MinValue != null ? $">= {item.Property.MinValue}" : ""), (item.Property.MinValue != null && item.Property.MaxValue != null ? " and " : ""), (item.Property.MaxValue != null ? $"<= {item.Property.MaxValue}" : "")); throw new ConfigurationErrorsException(s); } Items[name].Value = value; if(!IsLoadingFile) { NotifyPropertyChanged(name); } } } } /// /// Gets the specified name. /// /// /// The name. /// protected T Get([CallerMemberName] string name = null) { return (T)this[name]; } /// /// Sets the specified value. /// /// The value. /// The name. protected void Set(object value, [CallerMemberName] string name = null) { this[name] = value; } /// /// Existses the specified name. /// /// The name (case insensitive). /// public bool Exists(string name) => Items.ContainsKey(name); /// /// Gets the content of the file. /// /// /// hjnui private string GetFileContent() { return System.IO.File.ReadAllText(Options.FileName); } /// /// Saves the content of the file. /// private void SaveFileContent(string content) { System.IO.File.WriteAllText(Options.FileName, content); } /// /// Loads the item value. /// /// Name of the item. /// The raw value. private void LoadItemValue(string itemName, string rawValue) { object value = rawValue; if (Items.ContainsKey(itemName)) { var item = Items[itemName]; if (item.Property.IsEncrypted) { if (EncryptionHandler != null) { value = EncryptionHandler(rawValue, false); } else { value = EncryptionHelper.DecryptString(Options.EncryptPassword, rawValue); } if ((string)value == rawValue) //Value not encrypted { DataChanged = true; } } if (item.Property.IsSerialization) { value = JsonSerializer.Deserialize(((string)value).FromBase64(), item.PropertyType); } else { value = value.ChangeType(item.PropertyType); } this[itemName] = value; } else { Items.Add(itemName, new() { PropertyType = typeof(string), Value = value, Property = new()}); } } /// /// Loads the json configuration. /// private void LoadJsonConfig() { JsonSerializer.Deserialize>(GetFileContent()).ForEach(kv => LoadItemValue(kv.Key, kv.Value == null ? null : kv.Value.ToString())); } /// /// Loads the XML configuration. /// private void LoadXmlConfig() { using (System.Xml.XmlReader reader = XmlReader.Create(new StringReader(GetFileContent()))) { while (reader.Read() && !(reader.NodeType == XmlNodeType.Element && reader.LocalName.Equals("configuration", StringComparison.InvariantCultureIgnoreCase))) { } while (reader.Read()) { reader.MoveToContent(); if (reader.NodeType == XmlNodeType.Element) { string itemName = reader.Name; string itemValue = reader.ReadInnerXml(); LoadItemValue(itemName, itemValue); //data[itemName] = itemValue; } } } } /// /// Loads this settings /// /// if set to true [is automatic create]. public void Load(bool isAutoCreate = true) { if (isAutoCreate) { if (!File.Exists(Options.FileName)) { Save(); } } DataChanged = false; try { IsLoadingFile = true; if (Options.ConfigType == EasyConfigFileType.Json) { LoadJsonConfig(); } if (Options.ConfigType == EasyConfigFileType.Xml) { LoadXmlConfig(); } if (DataChanged) // Has encrypted value { Save(); } } finally { IsLoadingFile = false; } } /// /// Cammels the case. /// /// The key. /// private string CammelCase(string key) => key.Substitude(0, key.Substring(0, 1).ToLower()); /// /// Gets the save items. /// /// private Dictionary GetSaveItems() { Dictionary data = new(); JsonSerializerOptions options = new() { WriteIndented = true }; foreach (var item in Items.Where(it => !it.Value.Property.IsInternal)) { var itemName = item.Value.Property.Alias ?? CammelCase(item.Key); var property = item.Value.Property; var value = string.Empty; if (property.IsSerialization) { value = JsonSerializer.Serialize(item.Value.Value, item.Value.PropertyType, options: options); } else { value = item.Value.Value.StringFrom(); } if (property.IsEncrypted) { if (EncryptionHandler != null) { value = EncryptionHandler(value, true); } else { value = EncryptionHelper.EncryptString(value, Options.EncryptPassword); } } data[itemName] = value; } return data; } /// /// Saves the json configuration. /// private void SaveJsonConfig() { var data = GetSaveItems(); SaveFileContent(JsonSerializer.Serialize(data, data.GetType(), options: new() { WriteIndented = true })); } /// /// Saves the XML configuration. /// private void SaveXmlConfig() { var data = GetSaveItems(); XmlDocument doc = XmlHelper.CreateDocument("configuration"); XmlNode configNode = XmlHelper.SelectSingleNode(doc, "configuration"); foreach(var kp in data) { XmlHelper.AddSingleChildNode(configNode, kp.Key, kp.Value); } SaveFileContent(XmlHelper.DocToString(doc)); } /// /// Saves this settings /// public virtual void Save() { if (Options.ConfigType == EasyConfigFileType.Json) { SaveJsonConfig(); } if (Options.ConfigType == EasyConfigFileType.Xml) { SaveXmlConfig(); } DataChanged = false; } #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return Items.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return Items.Values.GetEnumerator(); } #endregion #region INotifyPropertyChanged Members /// /// Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String propertyName) { DataChanged = true; OnPropertyChanged(propertyName); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// Called when [property changed]. /// /// The name. protected virtual void OnPropertyChanged(string name) { } #endregion #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Items.Clear(); if (_AutoSaveTimer != null) { if (_AutoSaveTimer.Enabled) { AutoSaveElapsed(null, null); } _AutoSaveTimer.Dispose(); } } #endregion } }