SaveEvent.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. using Comal.Classes;
  2. using InABox.Core;
  3. using InABox.Database;
  4. using InABox.Scripting;
  5. using Newtonsoft.Json;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics.CodeAnalysis;
  9. using System.Linq;
  10. using System.Text;
  11. namespace PRS.Shared.Events;
  12. public class SaveEvent<T> : IEvent<SaveEventDataModel<T>>
  13. where T : Entity, new()
  14. {
  15. public Type Entity => typeof(T);
  16. public IEventDataModelDefinition DataModelDefinition()
  17. {
  18. return new SaveEventDataModelDefinition<T>(this);
  19. }
  20. public Notification GenerateNotification(SaveEventDataModel<T> model)
  21. {
  22. var notification = new Notification();
  23. notification.Title = $"Updated {typeof(T).Name}";
  24. notification.Description = $"Updated {typeof(T).Name}";
  25. if(model.Entity.ID != Guid.Empty)
  26. {
  27. notification.EntityType = CoreUtils.EntityName(model.EntityType);
  28. notification.EntityID = model.Entity.ID;
  29. }
  30. return notification;
  31. }
  32. public void Init(IStore store, IEventData evData, SaveEventDataModel<T> model)
  33. {
  34. if (model.Entity.ID != Guid.Empty)
  35. {
  36. var loadCols = Columns.None<T>();
  37. var prefix = $"{typeof(T).Name}.";
  38. foreach (var variable in evData.ReferencedVariables)
  39. {
  40. if (variable.StartsWith(prefix))
  41. {
  42. var varName = variable[prefix.Length..];
  43. if (!model.Entity.HasOriginalValue(varName))
  44. {
  45. loadCols.Add(varName);
  46. }
  47. }
  48. }
  49. var data = store.Provider.Query(
  50. new Filter<T>(x => x.ID).IsEqualTo(model.Entity.ID),
  51. loadCols);
  52. if(data.Rows.Count > 0)
  53. {
  54. data.Rows[0].FillObject(model.Entity);
  55. }
  56. }
  57. }
  58. }
  59. public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelDefinition
  60. where T : Entity, new()
  61. {
  62. private IEventVariable[]? variables;
  63. public SaveEvent<T> Event { get; set; } = ev;
  64. public IEnumerable<IEventVariable> GetVariables()
  65. {
  66. if(variables is null)
  67. {
  68. variables = DatabaseSchema.AllProperties(Event.Entity).Select(x => new StandardEventVariable($"{typeof(T).Name}.{x.Name}", x.PropertyType)).ToArray();
  69. variables.SortBy(x => x.Name);
  70. }
  71. return variables;
  72. }
  73. public IEventVariable? GetVariable(string name)
  74. {
  75. if(variables is null)
  76. {
  77. var prefix = $"{typeof(T).Name}.";
  78. if (name.StartsWith(prefix))
  79. {
  80. name = name[prefix.Length..];
  81. var prop = DatabaseSchema.Property(Event.Entity, name);
  82. if(prop is null)
  83. {
  84. return null;
  85. }
  86. else
  87. {
  88. return new StandardEventVariable(prop.Name, prop.PropertyType);
  89. }
  90. }
  91. else
  92. {
  93. return null;
  94. }
  95. }
  96. else
  97. {
  98. return variables.FirstOrDefault(x => x.Name == name);
  99. }
  100. }
  101. }
  102. public class SaveEventDataModel<T>(T entity) : IEventDataModel, ITypedEventDataModel
  103. where T : Entity, new()
  104. {
  105. public T Entity { get; set; } = entity;
  106. public Type EntityType => typeof(T);
  107. public bool TryGetVariable(string name, out object? value)
  108. {
  109. var prefix = $"{typeof(T).Name}.";
  110. if (name.StartsWith(prefix))
  111. {
  112. name = name[prefix.Length..];
  113. var prop = DatabaseSchema.Property(typeof(T), name);
  114. if(prop != null)
  115. {
  116. value = prop.Getter()(Entity);
  117. return true;
  118. }
  119. }
  120. value = null;
  121. return false;
  122. }
  123. }
  124. #region Triggers
  125. [Caption("New Record")]
  126. public class CreatedSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  127. where T : Entity, new()
  128. {
  129. public string Description => "New Record";
  130. public IEnumerable<string> ReferencedVariables => [];
  131. public bool Check(SaveEventDataModel<T> dataModel)
  132. {
  133. return dataModel.Entity.HasOriginalValue(x => x.ID);
  134. }
  135. }
  136. [Caption("Property Changed")]
  137. public class PropertyChangedSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  138. where T : Entity, new()
  139. {
  140. [JsonIgnore]
  141. public IProperty? TriggerProperty { get; set; }
  142. [JsonProperty(PropertyName = "TriggerProperty")]
  143. private string? _property
  144. {
  145. get => TriggerProperty?.Name;
  146. set
  147. {
  148. TriggerProperty = value is null ? null : DatabaseSchema.PropertyStrict(typeof(T), value);
  149. }
  150. }
  151. public string Description => TriggerProperty is null
  152. ? $"{typeof(T).GetCaption()} changed"
  153. : $"{typeof(T).GetCaption()}.{TriggerProperty.Name} changed";
  154. public IEnumerable<string> ReferencedVariables => [];
  155. public bool Check(SaveEventDataModel<T> dataModel)
  156. {
  157. if(TriggerProperty is null)
  158. {
  159. return false;
  160. }
  161. if (!dataModel.Entity.HasOriginalValue(TriggerProperty.Name))
  162. {
  163. return false;
  164. }
  165. return true;
  166. }
  167. }
  168. [Caption("Custom Script")]
  169. public class ScriptSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDataModel<T>>
  170. where T : Entity, new()
  171. {
  172. public string Description => "Custom Script";
  173. private ScriptDocument? _scriptDocument;
  174. private string? _script;
  175. public string? Script
  176. {
  177. get => _script;
  178. set
  179. {
  180. if(_script != value)
  181. {
  182. _script = value;
  183. _scriptDocument = null;
  184. }
  185. }
  186. }
  187. public IEnumerable<string> ReferencedVariables => [];
  188. public string DefaultScript()
  189. {
  190. return @"using PRS.Shared.Events;
  191. public class Module
  192. {
  193. public bool Check(SaveEventDataModel<" + typeof(T).Name + @"> model)
  194. {
  195. // Return true if model.Entity meets the requirements for this event trigger.
  196. return true;
  197. }
  198. }";
  199. }
  200. public bool Check(SaveEventDataModel<T> dataModel)
  201. {
  202. if (Script is null) return false;
  203. if(_scriptDocument is null)
  204. {
  205. _scriptDocument = new(Script);
  206. _scriptDocument.Compile();
  207. }
  208. return _scriptDocument.Execute(methodname: "Check", parameters: [dataModel]);
  209. }
  210. }
  211. #endregion
  212. #region Actions
  213. [Caption("Custom Script")]
  214. public class ScriptSaveEventAction<T> : IEventAction<SaveEvent<T>>
  215. where T : Entity, new()
  216. {
  217. private ScriptDocument? _scriptDocument;
  218. private string? _script;
  219. public string? Script
  220. {
  221. get => _script;
  222. set
  223. {
  224. if(_script != value)
  225. {
  226. _script = value;
  227. _scriptDocument = null;
  228. }
  229. }
  230. }
  231. public IEnumerable<string> ReferencedVariables => [];
  232. public string Description => "Custom Script";
  233. public string DefaultScript()
  234. {
  235. return @"using PRS.Shared.Events;
  236. public class Module
  237. {
  238. public object? Result { get; set; }
  239. public bool Execute(SaveEventDataModel<" + typeof(T).Name + @"> model)
  240. {
  241. // Do anything you want with model.Entity, and then save return-value to 'Result', or leave it as 'null' if no return value is needed.
  242. return true;
  243. }
  244. }";
  245. }
  246. public object? Execute(IEventDataModel dataModel)
  247. {
  248. if (Script is null) return null;
  249. if(_scriptDocument is null)
  250. {
  251. _scriptDocument = new(Script);
  252. _scriptDocument.SetValue("Result", null);
  253. _scriptDocument.Compile();
  254. }
  255. var model = dataModel.RootModel<SaveEventDataModel<T>>();
  256. if(_scriptDocument.Execute(methodname: "Execute", parameters: [model]))
  257. {
  258. return _scriptDocument.GetValue("Result");
  259. }
  260. else
  261. {
  262. return null;
  263. }
  264. }
  265. }
  266. [Caption("Create Entity")]
  267. public class CreateEntitySaveEventAction<T> : IEventAction<SaveEvent<T>>
  268. where T : Entity, new()
  269. {
  270. [JsonProperty(Order = 1)]
  271. public Type? EntityType { get; set; }
  272. [JsonIgnore]
  273. public List<PropertyInitializer> Initializers { get; set; } = new List<PropertyInitializer>();
  274. public string Description => $"Create New {EntityType?.Name ?? "Entity"}";
  275. public IEnumerable<string> ReferencedVariables => Initializers.SelectMany(x => x.ReferencedVariables);
  276. public object? Execute(IEventDataModel dataModel)
  277. {
  278. if(EntityType is null)
  279. {
  280. return null;
  281. }
  282. var entity = (Activator.CreateInstance(EntityType) as Entity)!;
  283. foreach(var initializer in Initializers)
  284. {
  285. initializer.Execute(entity, dataModel);
  286. }
  287. DbFactory.FindStore(EntityType, Guid.Empty, "", default, "", Logger.Main).Save(entity, "");
  288. return entity;
  289. }
  290. #region Serialization Stuff
  291. [JsonProperty(Order = 2, PropertyName = "Initializers")]
  292. private PropertyInitializerSerializationItem[] _initializers
  293. {
  294. get => Initializers.ToArray(x => new PropertyInitializerSerializationItem { Property = x.Property.Name, Value = x.Value });
  295. set
  296. {
  297. Initializers.Clear();
  298. Initializers.AddRange(value.Select(x => new PropertyInitializer(EntityType!, x)));
  299. }
  300. }
  301. #endregion
  302. }
  303. internal class PropertyInitializerSerializationItem
  304. {
  305. public string Property { get; set; }
  306. public string Value { get; set; }
  307. }
  308. public class PropertyInitializer
  309. {
  310. public IProperty Property { get; set; }
  311. private CoreExpression? _valueExpression;
  312. private CoreExpression ValueExpression
  313. {
  314. get
  315. {
  316. _valueExpression ??= new CoreExpression(Value, Property.PropertyType);
  317. return _valueExpression;
  318. }
  319. }
  320. private string _value;
  321. public string Value
  322. {
  323. get => _value;
  324. [MemberNotNull(nameof(_value))]
  325. set
  326. {
  327. if(value != _value)
  328. {
  329. _value = value;
  330. _valueExpression = null;
  331. }
  332. }
  333. }
  334. public IEnumerable<string> ReferencedVariables => ValueExpression.ReferencedVariables;
  335. internal PropertyInitializer(Type entityType, PropertyInitializerSerializationItem item)
  336. {
  337. Value = item.Value;
  338. Property = DatabaseSchema.PropertyStrict(entityType, item.Property);
  339. }
  340. public PropertyInitializer(IProperty property, string value)
  341. {
  342. Property = property;
  343. Value = value;
  344. }
  345. public void Execute(Entity entity, IEventDataModel dataModel)
  346. {
  347. Property.Setter()(entity, ValueExpression.Evaluate(dataModel));
  348. }
  349. }
  350. #endregion