| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810 | using com.sun.jmx.mbeanserver;using Comal.Classes;using Expressive;using InABox.Core;using InABox.Database;using Newtonsoft.Json;using Newtonsoft.Json.Serialization;using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics.CodeAnalysis;using System.Linq;using System.Reflection;using System.Text;using System.Threading.Tasks;namespace PRS.Shared.Events;public interface IEventData{    IEvent Event { get; }    IEnumerable<string> ReferencedVariables { get; }}public class EventData<T, TDataModel> : IEventData    where T : IEvent<TDataModel>    where TDataModel : IEventDataModel{    public T Event { get; set; }    /// <summary>    /// A list of triggers for this event. If any of the triggers match, the event runs.    /// </summary>    public List<IEventTrigger<T, TDataModel>> Triggers { get; set; }    public List<IEventAction<T>> Actions { get; set; }    IEvent IEventData.Event => Event;    public IEnumerable<string> ReferencedVariables =>        Triggers.SelectMany(x => x.ReferencedVariables)        .Concat(Actions.SelectMany(x => x.ReferencedVariables))        .Distinct();    public EventData(T eventData)    {        Event = eventData;        Triggers = new List<IEventTrigger<T, TDataModel>>();        Actions = new List<IEventAction<T>>();    }    public Notification GenerateNotification(TDataModel model) => Event.GenerateNotification(model);}public static class EventUtils{    #region Serialisation    private class WritablePropertiesOnlyResolver : DefaultContractResolver    {        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)        {            IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);            return props.Where(p => p.Writable).ToList();        }    }    private static JsonSerializerSettings SerializationSettings()    {        var settings = Serialization.CreateSerializerSettings();        settings.TypeNameHandling = TypeNameHandling.Auto;        settings.ContractResolver = new WritablePropertiesOnlyResolver();        return settings;    }    public static string Serialize(IEventData data)    {        return JsonConvert.SerializeObject(data, typeof(IEventData), SerializationSettings());    }    public static IEventData Deserialize(string json)    {        return JsonConvert.DeserializeObject<IEventData>(json, SerializationSettings())!;    }    #endregion    #region Event Types    private static Dictionary<Type, List<Type>>? _eventTriggerTypes;    private static Dictionary<Type, List<Type>>? _eventActionTypes;    private static List<Type>? _standardEventActionTypes;    [MemberNotNullWhen(true, nameof(_eventTriggerTypes), nameof(_eventActionTypes), nameof(_standardEventActionTypes))]    private static bool _loadedTypes { get; set; }    [MemberNotNull(nameof(_eventTriggerTypes), nameof(_eventActionTypes), nameof(_standardEventActionTypes))]    private static void LoadTypes()    {        if (_loadedTypes) return;        _loadedTypes = true;        _eventTriggerTypes = new();        _eventActionTypes = new();        _standardEventActionTypes = new();        foreach(var type in CoreUtils.TypeList(x => true))        {            if (type.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventTriggerInterface)            {                if (type.GetConstructor([]) is not null)                {                    var eventType = eventTriggerInterface.GenericTypeArguments[0];                    eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;                    _eventTriggerTypes.GetValueOrAdd(eventType).Add(type);                }            }            else if(type.GetInterfaceDefinition(typeof(IStandardEventAction<>)) is Type standardEventAction)            {                if (type.GetConstructor([]) is not null)                {                    _standardEventActionTypes.Add(type);                }            }            else if (type.GetInterfaceDefinition(typeof(IEventAction<>)) is Type eventActionInterface)            {                if (type.GetConstructor([]) is not null)                {                    var eventType = eventActionInterface.GenericTypeArguments[0];                    eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;                    _eventActionTypes.GetValueOrAdd(eventType).Add(type);                }            }        }    }    public static IEnumerable<Type> GetEventTriggerTypes(Type eventType)    {        LoadTypes();        eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;        return _eventTriggerTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();    }    public static IEnumerable<Type> GetEventActionTypes(Type eventType)    {        LoadTypes();        eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;        return _eventActionTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();    }    public static IEnumerable<Type> GetStandardEventActionTypes()    {        LoadTypes();        return _standardEventActionTypes;    }    #endregion    #region Execution    private static bool Check<T, TDataModel>(EventData<T, TDataModel> ev, TDataModel dataModel)        where T : IEvent<TDataModel>        where TDataModel : IEventDataModel    {        return ev.Triggers.Count == 0 || ev.Triggers.Any(x => x.Check(dataModel));    }    public static void Run<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)        where T : IEvent<TDataModel>        where TDataModel : IEventDataModel    {        if (!Check(evData, dataModel)) return;        foreach(var action in evData.Actions)        {            IEvent.RunAction(evData.Event, dataModel, action);        }        NotifySubscribers(store, ev, evData, dataModel);    }    public static void SendNotifications(IStore store, IEnumerable<(EventSubscriberType type, Notification notification)> notifications)    {        var toSave = new List<Notification>();        foreach(var (type, notification) in notifications)        {            if(type == EventSubscriberType.Notification)            {                toSave.Add(notification);            }            else if(type == EventSubscriberType.Email)            {                var mailer = DbFactory.Mailer;                if(mailer is not null)                {                    if (mailer.Connect())                    {                        var msg = mailer.CreateMessage();                        // msg.From = DbFactory.EmailAddress;                        msg.To = [notification.Employee.Email];                        msg.Subject = notification.Title;                        msg.Body = notification.Description;                        var bOK = mailer.SendMessage(msg);                    }                }            }        }        store.Provider.Save(toSave);    }    private static void NotifySubscribers<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)        where T : IEvent<TDataModel>        where TDataModel : IEventDataModel    {        if (!ev.NotificationsEnabled) return;        string? description;        if (ev.NotificationExpression.IsNullOrWhiteSpace())        {            description = null;        }        else        {            var descriptionExpr = new CoreExpression(ev.NotificationExpression, typeof(string));            if(!descriptionExpr.TryEvaluate<string>(dataModel).Get(out description, out var error))            {                CoreUtils.LogException(store.UserID, error, extra: "Error notifying subscribers", store.Logger);                return;            }        }        var subscribers = store.Provider.Query(            new Filter<EventSubscriber>(x => x.Event.ID).IsEqualTo(ev.ID),            Columns.None<EventSubscriber>().Add(x => x.Employee.ID).Add(x => x.Employee.Email).Add(x => x.SubscriberType))            .ToArray<EventSubscriber>();        SendNotifications(store, subscribers.Select(x =>        {            var notification = evData.GenerateNotification(dataModel);            notification.Employee.CopyFrom(x.Employee);            if (description is not null)            {                notification.Description = description;            }            return (x.SubscriberType, notification);        }));    }    #endregion    #region Event Cache    private static bool _loadedCache = false;    private static Dictionary<EventType, Dictionary<Type, List<(Event Event, IEventData EventData)>>> _entityEvents = new();    private static Dictionary<Guid, (EventType, Type)> _entityEventMap = new();    private static Dictionary<EventType, List<(Event Event, IEventData EventData)>> _genericEvents = new();    private static Dictionary<Guid, EventType> _genericEventMap = new();    public static Type GetEventType(EventType type)    {        switch (type)        {            case EventType.AfterSave:                return typeof(SaveEvent<>);            case EventType.Scheduled:                return typeof(ScheduledEvent);            default:                throw new Exception($"Invalid event type {type}");        }    }    public static void ReloadCache(IProvider provider)    {        _loadedCache = true;        _entityEvents.Clear();        _entityEventMap.Clear();        _genericEvents.Clear();        _genericEventMap.Clear();        var events = provider.Query(            new Filter<Event>(x => x.Enabled).IsEqualTo(true),            Columns.None<Event>().Add(x => x.ID).Add(x => x.Code).Add(x => x.EventType).Add(x => x.Data).Add(x => x.NotificationExpression).Add(x => x.NotificationsEnabled))            .ToObjects<Event>();        foreach(var ev in events)        {            AddEvent(provider, ev);        }    }    private static void AddGenericEvent(Event ev)    {        if(ev.Data is not null && ev.Data.Length != 0)        {            var data = Deserialize(ev.Data);            var list = _genericEvents.GetValueOrAdd(ev.EventType);            list.RemoveAll(x => x.Event.ID == ev.ID);                        list.Add((ev, data));            _genericEventMap[ev.ID] = ev.EventType;        }    }    private static void RemoveGenericEvent(Event ev)    {        if(_genericEventMap.Remove(ev.ID, out var evType))        {            if(_genericEvents.TryGetValue(evType, out var eventTypeEvents))            {                eventTypeEvents.RemoveAll(x => x.Event.ID == ev.ID);                if(eventTypeEvents.Count == 0)                {                    _genericEvents.Remove(evType);                }            }        }    }    private static void AddEntityEvent(Event ev)    {        if(ev.Data is not null && ev.Data.Length != 0)        {            var data = Deserialize(ev.Data);            var entityType = data.Event.GetType().GenericTypeArguments[0];            var list = _entityEvents.GetValueOrAdd(ev.EventType).GetValueOrAdd(entityType);            list.RemoveAll(x => x.Event.ID == ev.ID);                        list.Add((ev, data));            _entityEventMap[ev.ID] = (ev.EventType, entityType);        }    }    private static void RemoveEntityEvent(Event ev)    {        if(_entityEventMap.TryGetValue(ev.ID, out var item))        {            if(_entityEvents.TryGetValue(item.Item1, out var eventTypeEvents))            {                if(eventTypeEvents.TryGetValue(item.Item2, out var entityEvents))                {                    entityEvents.RemoveAll(x => x.Event.ID == ev.ID);                    if(entityEvents.Count == 0)                    {                        eventTypeEvents.Remove(item.Item2);                    }                }                if(eventTypeEvents.Count == 0)                {                    _entityEvents.Remove(item.Item1);                }            }            _entityEventMap.Remove(ev.ID);        }    }    public static void AddEvent(IProvider provider, Event ev)    {        if (!_loadedCache) ReloadCache(provider);        var type = GetEventType(ev.EventType);        if (type.HasInterface(typeof(IEntityEvent<>)))        {            AddEntityEvent(ev);        }        else        {            AddGenericEvent(ev);        }    }    public static void RemoveEvent(IProvider provider, Event ev)    {        if (!_loadedCache) ReloadCache(provider);        var type = GetEventType(ev.EventType);        if (type.HasInterface(typeof(IEntityEvent<>)))        {            RemoveEntityEvent(ev);        }        else        {            RemoveGenericEvent(ev);        }    }    #endregion    #region Public Event Running Interface    public static void AfterSave<T>(IStore store, T entity)        where T : Entity, new()    {        if (!_loadedCache) ReloadCache(store.Provider);        var events = _entityEvents.GetValueOrDefault(EventType.AfterSave)            ?.GetValueOrDefault(typeof(T));        if (events is null) return;        foreach(var ev in events)        {            var eventData = (ev.EventData as EventData<SaveEvent<T>, SaveEventDataModel<T>>)!;            var model = new SaveEventDataModel<T>(entity, store);            eventData.Event.Init(store, ev.Event, eventData, model);            Run(store, ev.Event, eventData, model);        }    }    private static bool CheckEventSchedule(ScheduledEvent ev)    {        var now = DateTime.Now;        switch (ev.Properties.Period)        {            case SchedulePeriod.Minute:            case SchedulePeriod.Hour:            case SchedulePeriod.Day:                var dayOfWeekSettings = ev.Properties.DayOfWeekSettings.FirstOrDefault(x => x.DayOfWeek == now.DayOfWeek);                if(dayOfWeekSettings is null || !dayOfWeekSettings.Enabled)                {                    return false;                }                var start = now - now.TimeOfDay + dayOfWeekSettings.StartTime;                var end = now - now.TimeOfDay + dayOfWeekSettings.EndTime;                if(ev.Properties.Period == SchedulePeriod.Day)                {                    if(ev.Properties.LastExecution < start && now >= start && (now.Date - ev.Properties.LastExecution.Date).TotalDays >= ev.Properties.Frequency)                    {                        ev.Properties.LastExecution = start;                        return true;                    }                    else                    {                        return false;                    }                }                else if(ev.Properties.Period == SchedulePeriod.Hour)                {                    if(start <= now && now <= end)                    {                        var nowIntervals = Math.Floor((now - start).TotalHours / ev.Properties.Frequency);                        var lastIntervals = Math.Floor((ev.Properties.LastExecution - start).TotalHours / ev.Properties.Frequency);                        if(nowIntervals > lastIntervals)                        {                            ev.Properties.LastExecution = start.AddHours(nowIntervals * ev.Properties.Frequency);                        }                        return true;                    }                    else                    {                        return false;                    }                }                else if(ev.Properties.Period == SchedulePeriod.Minute)                {                    if (start <= now && now <= end)                    {                        var nowIntervals = Math.Floor((now - start).TotalMinutes / ev.Properties.Frequency);                        var lastIntervals = Math.Floor((ev.Properties.LastExecution - start).TotalMinutes / ev.Properties.Frequency);                        if (nowIntervals > lastIntervals)                        {                            ev.Properties.LastExecution = start.AddMinutes(nowIntervals * ev.Properties.Frequency);                        }                        return true;                    }                    else                    {                        return false;                    }                }                else                {                    throw new Exception("Invalid state");                }            case SchedulePeriod.Week:                if (now >= ev.Properties.NextSchedule)                {                    while (now >= ev.Properties.NextSchedule)                    {                        ev.Properties.NextSchedule += TimeSpan.FromDays(7 * ev.Properties.Frequency);                    }                    return true;                }                else                {                    return false;                }            case SchedulePeriod.Month:                if(now >= ev.Properties.NextSchedule)                {                    while(now >= ev.Properties.NextSchedule)                    {                        ev.Properties.NextSchedule = ev.Properties.NextSchedule.AddMonths(ev.Properties.Frequency);                    }                    return true;                }                else                {                    return false;                }            case SchedulePeriod.Year:                if(now >= ev.Properties.NextSchedule)                {                    while(now >= ev.Properties.NextSchedule)                    {                        ev.Properties.NextSchedule = ev.Properties.NextSchedule.AddYears(ev.Properties.Frequency);                    }                    return true;                }                else                {                    return false;                }            default:                throw new Exception("Invalid state");        }    }    public static void CheckScheduledEvents()    {        var store = DbFactory.FindStore<Event>(Guid.Empty, "", Platform.DatabaseEngine, CoreUtils.GetVersion(), Logger.New());        if (!_loadedCache) ReloadCache(store.Provider);        var events = _genericEvents.GetValueOrDefault(EventType.Scheduled);        if (events is null) return;        foreach(var ev in events)        {            var eventData = (ev.EventData as EventData<ScheduledEvent, ScheduledEventDataModel>)!;            if (CheckEventSchedule(eventData.Event))            {                var model = new ScheduledEventDataModel(ev.Event);                Run(store, ev.Event, eventData, model);                ev.Event.Data = Serialize(eventData);                store.Provider.Save(ev.Event);            }        }    }    #endregion}#region DataModel Definitionpublic interface IEventVariable{    string Name { get; set; }    Type Type { get; set; }}public class StandardEventVariable : IEventVariable{    public string Name { get; set; }    public Type Type { get; set; }    public StandardEventVariable(string name, Type type)    {        Name = name;        Type = type;    }}public class ListEventVariable : IEventVariable{    public string Name { get; set; }    public Type Type { get; set; }    public ListEventVariable(string name, Type type)    {        Name = name;        Type = type;    }}public interface IEventDataModelDefinition{    IEnumerable<IEventVariable> GetVariables();    IEventVariable? GetVariable(string name);}#endregion#region DataModelpublic interface IEventDataModel : IVariableProvider{    bool TryGetVariable(string name, out object? value);    bool IVariableProvider.TryGetValue(string variableName, out object? value)    {        return TryGetVariable(variableName, out value);    }    T RootModel<T>()        where T : IEventDataModel    {        if(this is IChildEventDataModel child)        {            return child.Parent.RootModel<T>();        }        else if(this is T root)        {            return root;        }        else        {            throw new Exception($"Root model of wrong type; expected {typeof(T).FullName}; got {GetType().FullName}");        }    }}public interface ITypedEventDataModel : IEventDataModel{    public Type EntityType { get; }}public interface IChildEventDataModel : IEventDataModel{    IEventDataModel Parent { get; }}public class ChildEventDataModel : IChildEventDataModel{    public IEventDataModel Parent { get; set; }    public Dictionary<string, object?> Values { get; set; } = new Dictionary<string, object?>();    public ChildEventDataModel(IEventDataModel parent)    {        Parent = parent;    }    public bool TryGetVariable(string name, out object? value)    {        return Values.TryGetValue(name, out value)            || Parent.TryGetVariable(name, out value);    }    public void Init(IEventData data)    {    }}#endregionpublic interface IEvent{    IEventDataModelDefinition DataModelDefinition();    public static void RunAction(IEvent ev, IEventDataModel model, IEventAction action)    {        var dataModelDef = ev.DataModelDefinition();        var values = new List<(string name, object? value)[]>() { Array.Empty<(string, object?)>() };        foreach(var variable in action.ReferencedVariables)        {            var varDef = dataModelDef.GetVariable(variable);            if(varDef is ListEventVariable)            {                if (model.TryGetVariable(varDef.Name, out var list) && list is IEnumerable enumerable)                {                    var oldValues = values;                    values = new List<(string name, object? value)[]>();                    foreach(var item in enumerable)                    {                        foreach(var valueList in oldValues)                        {                            values.Add(valueList.Concatenate(new (string name, object? value)[]                            {                                (varDef.Name, item)                            }));                        }                    }                }                else                {                    values.Clear();                    break;                }            }        }        if(values.Count > 0 && (values.Count > 1 || values[0].Length > 0))        {            var subModel = new ChildEventDataModel(model);            foreach(var valueSet in values)            {                subModel.Values.Clear();                foreach(var (name, value) in valueSet)                {                    subModel.Values[name] = value;                }                action.Execute(subModel);            }        }        else        {            action.Execute(model);        }    }}/// <summary>/// Indicates that this event type has properties that can be edited; <typeparamref name="TProperties"/> is used to edit these properties/// with a dynamic editor./// </summary>public interface IPropertiesEvent<TProperties>    where TProperties : BaseObject, new(){    /// <summary>    /// Create the properties object for editing.    /// </summary>    TProperties GetProperties();    /// <summary>    /// Given an edited properties object, save them again.    /// </summary>    void SetProperties(TProperties properties);}/// <summary>/// Indicates that this event is generic for type <typeparamref name="T"/>; if this interface is implemented,/// the main type <b>must</b> be generic, with only one generic type argument./// </summary>public interface IEntityEvent<T>    where T : Entity{}public interface IEvent<TDataModel> : IEvent{    Notification GenerateNotification(TDataModel model);}public interface IEventTrigger{    IEnumerable<string> ReferencedVariables { get; }    string Description { get; }}public interface IEventTrigger<TEvent, TDataModel> : IEventTrigger    where TEvent : IEvent<TDataModel>    where TDataModel : IEventDataModel{    bool Check(TDataModel dataModel);}public interface IEventTriggerParent<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>    where TEvent : IEvent<TDataModel>    where TDataModel : IEventDataModel{    public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; }}public class AndTrigger<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>, IEventTriggerParent<TEvent, TDataModel>    where TEvent : IEvent<TDataModel>    where TDataModel : IEventDataModel{    public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; set; } = new();    public IEnumerable<string> ReferencedVariables => ChildTriggers.SelectMany(x => x.ReferencedVariables);    public string Description => "And";    public bool Check(TDataModel dataModel)    {        return ChildTriggers.All(x => x.Check(dataModel));    }}public class OrTrigger<TEvent, TDataModel> : IEventTrigger<TEvent, TDataModel>, IEventTriggerParent<TEvent, TDataModel>    where TEvent : IEvent<TDataModel>    where TDataModel : IEventDataModel{    public List<IEventTrigger<TEvent, TDataModel>> ChildTriggers { get; set; } = new();    public IEnumerable<string> ReferencedVariables => ChildTriggers.SelectMany(x => x.ReferencedVariables);    public string Description => "Or";    public bool Check(TDataModel dataModel)    {        return ChildTriggers.Any(x => x.Check(dataModel));    }}public interface IEventAction{    IEnumerable<string> ReferencedVariables { get; }    object? Execute(IEventDataModel dataModel);    string Description { get; }}public interface IEventAction<TEvent> : IEventAction    where TEvent : IEvent{}/// <summary>/// Marks this event action as being applicable to any event type./// </summary>public interface IStandardEventAction<TEvent> : IEventAction<TEvent>    where TEvent : IEvent{}
 |