|
@@ -0,0 +1,203 @@
|
|
|
+
|
|
|
+namespace Yuuna.Common.Configuration
|
|
|
+{
|
|
|
+ using Newtonsoft.Json;
|
|
|
+ using Newtonsoft.Json.Linq;
|
|
|
+ using System;
|
|
|
+ using System.Collections.Generic;
|
|
|
+ using System.ComponentModel;
|
|
|
+ using System.Dynamic;
|
|
|
+ using System.IO;
|
|
|
+ using System.Reflection;
|
|
|
+ using System.Security.Cryptography;
|
|
|
+ using System.Text;
|
|
|
+ using Yuuna.Common.Serialization;
|
|
|
+
|
|
|
+
|
|
|
+ public sealed class ConfigData<T> : DynamicObject, INotifyPropertyChanged
|
|
|
+ {
|
|
|
+ public override IEnumerable<string> GetDynamicMemberNames()
|
|
|
+ {
|
|
|
+ return this._list.Keys;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
|
|
|
+ {
|
|
|
+ if (this._list.TryGetValue(binder.Name, out var accessor))
|
|
|
+ {
|
|
|
+ result = accessor.Get();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ throw new KeyNotFoundException(binder.Name);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TrySetMember(SetMemberBinder binder, object value)
|
|
|
+ {
|
|
|
+ if(this._list.TryGetValue(binder.Name, out var o))
|
|
|
+ {
|
|
|
+ var re = o.Get();
|
|
|
+ if (re is null && value is null)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ if (!re?.Equals(value) ?? !value?.Equals(re) ?? !re.Equals(value))
|
|
|
+ {
|
|
|
+ this._list[binder.Name].Set(value);
|
|
|
+ this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(binder.Name));
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ throw new KeyNotFoundException(binder.Name);
|
|
|
+ }
|
|
|
+
|
|
|
+ private readonly IDictionary<string, Accessor> _list;
|
|
|
+
|
|
|
+ private static string GetName(Type type)
|
|
|
+ {
|
|
|
+ return type.Name + ".meta";
|
|
|
+ }
|
|
|
+
|
|
|
+ private readonly FileInfo _meta;
|
|
|
+
|
|
|
+ public void Load()
|
|
|
+ {
|
|
|
+
|
|
|
+ using (var reader = new StreamReader(this._meta.OpenRead()))
|
|
|
+ {
|
|
|
+ var jObj = JsonConvert.DeserializeObject<JObject>(reader.ReadToEnd());
|
|
|
+ foreach (var jProp in jObj)
|
|
|
+ {
|
|
|
+ if (this._list.TryGetValue(jProp.Key, out var a))
|
|
|
+ {
|
|
|
+ a.Set(jProp.Value.ToObject(a.Type));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Save()
|
|
|
+ {
|
|
|
+ using (var writer = new StreamWriter(this._meta.OpenWrite()))
|
|
|
+ {
|
|
|
+ var j = new JObject();
|
|
|
+
|
|
|
+
|
|
|
+ foreach (var accessor in this._list)
|
|
|
+ {
|
|
|
+ j.Add(new JProperty(accessor.Key, accessor.Value.Get()));
|
|
|
+ }
|
|
|
+
|
|
|
+ var json = JsonConvert.SerializeObject(j, Formatting.Indented);
|
|
|
+ writer.Write(json);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public ConfigData(T graph)
|
|
|
+ {
|
|
|
+ var type = graph?.GetType() ?? throw new ArgumentNullException(nameof(graph));
|
|
|
+ this._list = new Dictionary<string, Accessor>();
|
|
|
+ this._meta = new FileInfo(GetName(type));
|
|
|
+
|
|
|
+
|
|
|
+ foreach (var pinfo in type.GetProperties((BindingFlags)52))
|
|
|
+ if (pinfo.GetCustomAttribute<FieldAttribute>() != null &&
|
|
|
+
|
|
|
+ pinfo.GetGetMethod(true) is MethodInfo getter &&
|
|
|
+ pinfo.GetSetMethod(true) is MethodInfo setter)
|
|
|
+ {
|
|
|
+ var getDele = Delegate.CreateDelegate(typeof(Func<>).MakeGenericType(getter.ReturnType), graph, getter);
|
|
|
+ var setDele = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(setter.GetParameters()[0].ParameterType), graph, setter);
|
|
|
+ this._list.Add(pinfo.Name, new Accessor(pinfo.PropertyType , getDele, setDele));
|
|
|
+ }
|
|
|
+
|
|
|
+ // metadata not found, create and serialize object to file.
|
|
|
+ if (!this._meta.Exists)
|
|
|
+ {
|
|
|
+ this.Save();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this.Load();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private struct Accessor
|
|
|
+ {
|
|
|
+ private readonly Delegate _get;
|
|
|
+ private readonly Delegate _set;
|
|
|
+
|
|
|
+ public Accessor(Type type,Delegate get, Delegate set)
|
|
|
+ {
|
|
|
+ this.Type = type;
|
|
|
+ this._get = get;
|
|
|
+ this._set = set;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Type Type { get; }
|
|
|
+
|
|
|
+ public object Get() => this._get.DynamicInvoke();
|
|
|
+ public void Set(object value) => this._set.DynamicInvoke(value);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public IEnumerable<string> Names => this._list.Keys;
|
|
|
+
|
|
|
+
|
|
|
+ public event PropertyChangedEventHandler PropertyChanged;
|
|
|
+
|
|
|
+ public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
|
|
|
+ {
|
|
|
+ if (indexes.Length.Equals(1) &&
|
|
|
+ indexes[0] is string name)
|
|
|
+ {
|
|
|
+ if (this._list.TryGetValue(name, out var o))
|
|
|
+ {
|
|
|
+ var re = o.Get();
|
|
|
+ if (re is null && value is null)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ if (!re?.Equals(value) ?? !value?.Equals(re) ?? !re.Equals(value))
|
|
|
+ {
|
|
|
+ this._list[name].Set(value);
|
|
|
+ this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ throw new KeyNotFoundException(name);
|
|
|
+ }
|
|
|
+ throw new ArgumentOutOfRangeException();
|
|
|
+ }
|
|
|
+ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
|
|
|
+ {
|
|
|
+ if(indexes.Length.Equals(1) &&
|
|
|
+ indexes[0] is string name )
|
|
|
+ {
|
|
|
+ if(this._list.TryGetValue(name, out var o))
|
|
|
+ {
|
|
|
+ result = o.Get();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ throw new KeyNotFoundException(name);
|
|
|
+ }
|
|
|
+ throw new ArgumentOutOfRangeException();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class ConfigExtension
|
|
|
+ {
|
|
|
+ public static dynamic Bind<T>(this T obj, PropertyChangedEventHandler onPropertyChnaged = null, bool autoSave = true)
|
|
|
+ {
|
|
|
+ var p = new ConfigData<T>(obj);
|
|
|
+ if (onPropertyChnaged != null)
|
|
|
+ p.PropertyChanged += onPropertyChnaged;
|
|
|
+ if (autoSave)
|
|
|
+ p.PropertyChanged += delegate { p.Save(); };
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [AttributeUsage(AttributeTargets.Property)]
|
|
|
+ public sealed class FieldAttribute : Attribute
|
|
|
+ {
|
|
|
+ }
|
|
|
+}
|