using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; //using PropertyChanged; namespace InABox.Core { public class DoNotSerialize : Attribute { } public interface IBaseObject { public bool IsChanged(); public void CancelChanges(); public void CommitChanges(); public bool IsObserving(); public void SetObserving(bool active); } /// /// Observable object with INotifyPropertyChanged implemented /// public abstract class BaseObject : INotifyPropertyChanged, IBaseObject { public BaseObject() { SetObserving(false); Init(); SetObserving(true); } [OnDeserializing] internal void OnDeserializingMethod(StreamingContext context) { if (_observing) SetObserving(false); } [OnDeserialized] internal void OnDeserializedMethod(StreamingContext context) { if (!_observing) SetObserving(true); } protected virtual void Init() { CheckOriginalValues(); UserProperties = new UserProperties(); //UserProperties.ParentType = this.GetType(); UserProperties.OnPropertyChanged += (o, n, b, a) => { if (IsObserving()) OnPropertyChanged(n, b, a); }; DatabaseSchema.InitializeObject(this); } #region Observing Flags public static bool GlobalObserving = true; private bool _observing = true; public bool IsObserving() { return GlobalObserving && _observing; } public void SetObserving(bool active) { bApplyingChanges = true; //UserProperties.ParentType = this.GetType(); _observing = active; foreach (var oo in CoreUtils.GetChildren(this)) oo.SetObserving(active); /*UserProperties.OnPropertyChanged += (o, n, b, a) => { if (_observing) OnPropertyChanged(n, b, a); };*/ bApplyingChanges = false; } protected virtual void DoPropertyChanged(string name, object? before, object? after) { } public event PropertyChangedEventHandler? PropertyChanged; private bool bApplyingChanges; private bool bChanged; [DoNotPersist] public ConcurrentDictionary OriginalValues { get; set; } protected virtual void SetChanged(string name, object? before, object? after) { bChanged = true; if (!bApplyingChanges) { bApplyingChanges = true; DoPropertyChanged(name, before, after); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); bApplyingChanges = false; } } private bool QueryChanged() { CheckOriginalValues(); if (OriginalValues.Count > 0) return true; foreach (var oo in CoreUtils.GetChildren(this)) if (oo.IsChanged()) return true; return false; } private bool HasChanged(object? before, object? after) { if ((before == null || before.Equals("")) && (after == null || after.Equals(""))) return false; if (before == null != (after == null)) return true; if (!before!.GetType().Equals(after!.GetType())) return true; if (before is string[] && after is string[]) return !(before as string[]).SequenceEqual(after as string[]); return !before.Equals(after); } public void OnPropertyChanged(string name, object? before, object? after) { if (!IsObserving()) return; if (name.Equals("IsChanged")) return; if (name.Equals("Observing")) return; if (name.Equals("OriginalValues")) return; if (!HasChanged(before, after)) return; CheckOriginalValues(); if (!OriginalValues.ContainsKey(name)) OriginalValues[name] = before; SetChanged(name, before, after); } private void CheckOriginalValues() { if (OriginalValues == null) { var bObserving = _observing; _observing = false; OriginalValues = new ConcurrentDictionary(); _observing = bObserving; } } public bool IsChanged() { return IsObserving() ? QueryChanged() : bChanged; } public void CancelChanges() { bApplyingChanges = true; var bObs = IsObserving(); SetObserving(false); CheckOriginalValues(); foreach (var key in OriginalValues.Keys.ToArray()) { try { var prop = key.Contains(".") ? CoreUtils.GetProperty(GetType(),key) : GetType().GetRuntimeProperty(key); if(prop != null) { if (prop.SetMethod != null) { var val = OriginalValues[key]; // Funky 64bit stuff here? if (prop.PropertyType == typeof(int) && val?.GetType() == typeof(long)) val = Convert.ToInt32(val); prop.SetValue(this, val); } } else if (UserProperties.ContainsKey(key)) { UserProperties[key] = OriginalValues[key]; } else { Logger.Send(LogType.Error, "", $"'{key}' is neither a runtime property nor custom property of {GetType().Name}"); } } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } OriginalValues.Clear(); bChanged = false; foreach (var oo in CoreUtils.GetChildren(this)) oo.CancelChanges(); SetObserving(bObs); bApplyingChanges = false; } public void CommitChanges() { bApplyingChanges = true; CheckOriginalValues(); OriginalValues.Clear(); bChanged = false; foreach (var oo in CoreUtils.GetChildren(this)) oo.CommitChanges(); bApplyingChanges = false; } public string ChangedValues() { var result = new List(); var type = GetType(); try { CheckOriginalValues(); foreach (var key in OriginalValues.Keys) try { if (UserProperties.ContainsKey(key)) { var obj = UserProperties[key]; result.Add(string.Format("[{0} = {1}]", key, obj != null ? obj.ToString() : "null")); } else { var prop = DatabaseSchema.Property(type, key);// GetType().GetProperty(key); if (prop is StandardProperty standard && standard.Loggable != null) { /*var attribute = //prop.GetCustomAttributes(typeof(LoggablePropertyAttribute), true).FirstOrDefault(); if (attribute != null) {*/ //var lpa = (LoggablePropertyAttribute)attribute; var format = standard.Loggable.Format; var value = standard.Getter()(this); if (string.IsNullOrEmpty(format)) result.Add($"[{key} = {value}]"); else result.Add(string.Format("[{0} = {1:" + format + "}]", key, value)); //} } } } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } return string.Join(" ", result); } #endregion #region UserProperties private UserProperties _userproperties; public UserProperties UserProperties { get { CheckUserProperties(); return _userproperties; } set { _userproperties = value; CheckUserProperties(); } } private static Dictionary? DefaultProperties; private void CheckUserProperties() { if (_userproperties == null) { _userproperties = new UserProperties(); if (DefaultProperties == null) { DefaultProperties = new Dictionary(); var props = DatabaseSchema.Properties(GetType()).Where(x => x is CustomProperty).ToArray(); foreach (var field in props) DefaultProperties[field.Name] = DatabaseSchema.DefaultValue(field.PropertyType); } _userproperties.LoadFromDictionary(DefaultProperties); } } #endregion } public static class BaseObjectExtensions { public static bool HasOriginalValue(this T sender, string propertyname) where T : BaseObject { return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname); } public static TType GetOriginalValue(this T sender, string propertyname) where T : BaseObject { return sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propertyname) ? (TType)CoreUtils.ChangeType(sender.OriginalValues[propertyname], typeof(TType)) : default; } public static Dictionary GetValues(this T sender, bool all) where T : BaseObject { var result = new Dictionary(); LoadValues(sender, result, "", all, false); return result; } public static Dictionary GetOriginaValues(this T sender, bool all) where T : BaseObject { var result = new Dictionary(); LoadValues(sender, result, "", all, true); return result; } private static void LoadValues(BaseObject sender, Dictionary values, string prefix, bool all, bool original) { try { var props = sender.GetType().GetProperties().Where(x => x.GetCustomAttribute() == null && x.GetCustomAttribute() == null && x.GetCustomAttribute() == null && x.GetCustomAttribute() == null && x.GetCustomAttribute() == null && x.CanWrite); foreach (var prop in props) if (prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity))) { var child = prop.GetValue(sender) as BaseObject; if (child != null) LoadValues(child, values, string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name, all, original); } else if (prop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))) { var child = prop.GetValue(sender) as BaseObject; if (child != null) { if (all) { values[string.IsNullOrWhiteSpace(prefix) ? prop.Name + ".ID" : prefix + "." + prop.Name + ".ID"] = original ? child.OriginalValues.ContainsKey("ID") ? child.OriginalValues["ID"] : Guid.Empty : CoreUtils.GetPropertyValue(child, "ID"); } else { if (child.HasOriginalValue("ID")) values[string.IsNullOrWhiteSpace(prefix) ? prop.Name + ".ID" : prefix + "." + prop.Name + ".ID"] = original ? child.OriginalValues.ContainsKey("ID") ? child.OriginalValues["ID"] : Guid.Empty : CoreUtils.GetPropertyValue(child, "ID"); } } } else if (prop.PropertyType.GetInterfaces().Contains(typeof(IBaseObject))) { var child = prop.GetValue(sender) as IBaseObject; if (child != null) { if (all) { values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = child; } else { if (child.IsChanged()) values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = child; } } } else { if (all) { values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = original ? sender.OriginalValues.ContainsKey(prop.Name) ? sender.OriginalValues[prop.Name] : prop.PropertyType.GetDefault() : prop.GetValue(sender); } else { if (sender.HasOriginalValue(prop.Name)) values[string.IsNullOrWhiteSpace(prefix) ? prop.Name : prefix + "." + prop.Name] = original ? sender.OriginalValues.ContainsKey(prop.Name) ? sender.OriginalValues[prop.Name] : prop.PropertyType.GetDefault() : prop.GetValue(sender); } } var iprops = DatabaseSchema.Properties(sender.GetType()).Where(x => x is CustomProperty); foreach (var iprop in iprops) if (all || sender.HasOriginalValue(iprop.Name)) values[string.IsNullOrWhiteSpace(prefix) ? iprop.Name : prefix + "." + iprop.Name] = original ? sender.OriginalValues.ContainsKey(iprop.Name) ? sender.OriginalValues[iprop.Name] : iprop.PropertyType.GetDefault() : sender.UserProperties[iprop.Name]; } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } } public static List Compare(this T sender, Dictionary original) where T : BaseObject { var result = new List(); var current = GetValues(sender, true); foreach (var key in current.Keys) if (original.ContainsKey(key)) { if (current[key] == null) { if (original[key] != null) result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key])); } else { if (!current[key].Equals(original[key])) result.Add(string.Format("[{0}] has changed from [{1}] to [{2}]", key, original[key], current[key])); } } else { result.Add(string.Format("[{0}] not present in previous dictionary!", key)); } return result; } public static bool GetValue(this T sender, Expression> property, bool original, out TType result) where T : BaseObject { if (sender.HasOriginalValue(property)) { if (original) result = sender.GetOriginalValue(property); else { var expr = property.Compile(); result = expr(sender); } return true; } result = default(TType); return false; } public static void SetOriginalValue(this T sender, string propertyname, TType value) where T : BaseObject { sender.OriginalValues[propertyname] = value; } public static bool HasOriginalValue(this T sender, Expression> property) where T : BaseObject { //var prop = ((MemberExpression)property.Body).Member as PropertyInfo; String propname = CoreUtils.GetFullPropertyName(property, "."); return !String.IsNullOrWhiteSpace(propname) && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(propname); } public static TType GetOriginalValue(this T sender, Expression> property) where T : BaseObject { var prop = ((MemberExpression)property.Body).Member as PropertyInfo; return prop != null && sender.OriginalValues != null && sender.OriginalValues.ContainsKey(prop.Name) ? (TType)CoreUtils.ChangeType(sender.OriginalValues[prop.Name], typeof(TType)) : default; } public static void SetOriginalValue(this T sender, Expression> property, TType value) where T : BaseObject { var prop = ((MemberExpression)property.Body).Member as PropertyInfo; sender.OriginalValues[prop.Name] = value; } } /// /// An is loggable if it has the defined on it.
/// If it is part of an , then it is only loggable if the property on the parent class /// also has . ///
public class LoggablePropertyAttribute : Attribute { public string Format { get; set; } } }