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
}
}