123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- 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
- {
- /// <summary>
- /// Easy Configuration file
- /// </summary>
- public class EasyConfiguration : INotifyPropertyChanged, IEnumerable<EasyConfigItem>, IDisposable
- {
- System.Timers.Timer _AutoSaveTimer;
- private bool _DataChanged = false;
- /// <summary>
- /// Gets or sets a value indicating whether this instance is loading file.
- /// </summary>
- /// <value>
- /// <c>true</c> if this instance is loading file; otherwise, <c>false</c>.
- /// </value>
- 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;
- }
- }
- /// <summary>
- /// Gets the options.
- /// </summary>
- /// <value>
- /// The options.
- /// </value>
- public EasyConfigOptions Options { get; private set; }
- /// <summary>
- /// The items
- /// </summary>
- protected Dictionary<string, EasyConfigItem> Items { get; private set; } = new Dictionary<string, EasyConfigItem>(StringComparer.InvariantCultureIgnoreCase);
- /// <summary>
- /// Prevents a default instance of the <see cref="EasyConfiguration"/> class from being created.
- /// </summary>
- /// <exception cref="System.ArgumentNullException"></exception>
- /// <exception cref="System.NotImplementedException"></exception>
- /// <exception cref="System.NotSupportedException"></exception>
- private EasyConfiguration()
- {
- LoadDefaultConfig();
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="EasyConfiguration" /> class.
- /// </summary>
- /// <param name="options">The options.</param>
- 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;
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="EasyConfiguration" /> class.
- /// </summary>
- /// <param name="configFile">The configuration file.</param>
- /// <param name="password">The password.</param>
- public EasyConfiguration(string configFile, string password = "") : this(new() { FileName = configFile, EncryptPassword = password })
- {
- }
- /// <summary>
- /// Loads the default config.
- /// </summary>
- 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;
- }
- /// <summary>
- /// Clears this instance.
- /// </summary>
- public void Clear()
- {
- Items.Clear();
- LoadDefaultConfig();
- }
- /// <summary>
- /// Gets or sets the encryption handler.
- /// </summary>
- /// <value>
- /// The encryption handler (string: value, bool: true/false - Encrypted/Decrypted).
- /// </value>
- public Func<string, bool, string> EncryptionHandler { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether [data changed].
- /// </summary>
- /// <value>
- /// <c>true</c> if [data changed]; otherwise, <c>false</c>.
- /// </value>
- public bool DataChanged
- {
- get
- {
- return _DataChanged;
- }
- protected set
- {
- _DataChanged = value;
- if (_AutoSaveTimer != null)
- {
- if (DataChanged)
- {
- _AutoSaveTimer.Stop();
- _AutoSaveTimer.Start();
- }
- else
- {
- _AutoSaveTimer.Stop();
- }
- }
- }
- }
- /// <summary>
- /// Automatics the save elapsed.
- /// </summary>
- /// <param name="sender">The sender.</param>
- /// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
- private void AutoSaveElapsed(object sender, System.Timers.ElapsedEventArgs e)
- {
- _AutoSaveTimer.Stop();
- lock (this)
- {
- this.Save();
- }
- }
- /// <summary>
- /// Gets or sets the <see cref="System.Object" /> with the specified name.
- /// </summary>
- /// <value>
- /// The <see cref="System.Object" />.
- /// </value>
- /// <param name="name">The name (case insensitive).</param>
- /// <returns></returns>
- /// <exception cref="ConfigurationErrorsException"></exception>
- /// <exception cref="System.NotSupportedException"></exception>
- 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);
- }
- }
- }
- }
- /// <summary>
- /// Gets the specified name.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="name">The name.</param>
- /// <returns></returns>
- protected T Get<T>([CallerMemberName] string name = null)
- {
- return (T)this[name];
- }
- /// <summary>
- /// Sets the specified value.
- /// </summary>
- /// <param name="value">The value.</param>
- /// <param name="name">The name.</param>
- protected void Set(object value, [CallerMemberName] string name = null)
- {
- this[name] = value;
- }
- /// <summary>
- /// Existses the specified name.
- /// </summary>
- /// <param name="name">The name (case insensitive).</param>
- /// <returns></returns>
- public bool Exists(string name) => Items.ContainsKey(name);
- /// <summary>
- /// Gets the content of the file.
- /// </summary>
- /// <returns></returns>
- /// hjnui
- private string GetFileContent()
- {
- return System.IO.File.ReadAllText(Options.FileName);
- }
- /// <summary>
- /// Saves the content of the file.
- /// </summary>
- private void SaveFileContent(string content)
- {
- System.IO.File.WriteAllText(Options.FileName, content);
- }
- /// <summary>
- /// Loads the item value.
- /// </summary>
- /// <param name="itemName">Name of the item.</param>
- /// <param name="rawValue">The raw value.</param>
- 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()});
- }
- }
- /// <summary>
- /// Loads the json configuration.
- /// </summary>
- private void LoadJsonConfig()
- {
- JsonSerializer.Deserialize<Dictionary<string, object>>(GetFileContent()).ForEach(kv => LoadItemValue(kv.Key, kv.Value == null ? null : kv.Value.ToString()));
- }
- /// <summary>
- /// Loads the XML configuration.
- /// </summary>
- 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;
- }
- }
- }
- }
- /// <summary>
- /// Loads this settings
- /// </summary>
- /// <param name="isAutoCreate">if set to <c>true</c> [is automatic create].</param>
- 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;
- }
- }
- /// <summary>
- /// Cammels the case.
- /// </summary>
- /// <param name="key">The key.</param>
- /// <returns></returns>
- private string CammelCase(string key) => key.Substitude(0, key.Substring(0, 1).ToLower());
- /// <summary>
- /// Gets the save items.
- /// </summary>
- /// <returns></returns>
- private Dictionary<string, string> GetSaveItems()
- {
- Dictionary<string, string> 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;
- }
- /// <summary>
- /// Saves the json configuration.
- /// </summary>
- private void SaveJsonConfig()
- {
- var data = GetSaveItems();
- SaveFileContent(JsonSerializer.Serialize(data, data.GetType(), options: new() { WriteIndented = true }));
- }
- /// <summary>
- /// Saves the XML configuration.
- /// </summary>
- 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));
- }
- /// <summary>
- /// Saves this settings
- /// </summary>
- public virtual void Save()
- {
- if (Options.ConfigType == EasyConfigFileType.Json)
- {
- SaveJsonConfig();
- }
- if (Options.ConfigType == EasyConfigFileType.Xml)
- {
- SaveXmlConfig();
- }
- DataChanged = false;
- }
- #region IEnumerable<EasyConfigurationItem> Members
- IEnumerator<EasyConfigItem> IEnumerable<EasyConfigItem>.GetEnumerator()
- {
- return Items.Values.GetEnumerator();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return Items.Values.GetEnumerator();
- }
- #endregion
- #region INotifyPropertyChanged Members
- /// <summary>
- /// Occurs when a property value changes.
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
- private void NotifyPropertyChanged(String propertyName)
- {
- DataChanged = true;
- OnPropertyChanged(propertyName);
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- /// <summary>
- /// Called when [property changed].
- /// </summary>
- /// <param name="name">The name.</param>
- protected virtual void OnPropertyChanged(string name)
- {
- }
- #endregion
- #region IDisposable Members
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- Items.Clear();
- if (_AutoSaveTimer != null)
- {
- if (_AutoSaveTimer.Enabled)
- {
- AutoSaveElapsed(null, null);
- }
- _AutoSaveTimer.Dispose();
- }
- }
- #endregion
- }
- }
|