|
@@ -245,14 +245,32 @@ public static class EventUtils
|
|
|
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))
|
|
|
+ Columns.None<Event>().Add(x => x.ID).Add(x => x.Code).Add(x => x.EventType).Add(x => x.Data).Add(x => x.NotificationExpression))
|
|
|
.ToObjects<Event>();
|
|
|
foreach(var ev in events)
|
|
|
{
|
|
@@ -260,6 +278,33 @@ public static class EventUtils
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ 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)
|
|
@@ -297,26 +342,32 @@ public static class EventUtils
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static void RemoveEvent(IProvider provider, Event ev)
|
|
|
+ public static void AddEvent(IProvider provider, Event ev)
|
|
|
{
|
|
|
if (!_loadedCache) ReloadCache(provider);
|
|
|
|
|
|
- switch (ev.EventType)
|
|
|
+ var type = GetEventType(ev.EventType);
|
|
|
+ if (type.HasInterface(typeof(IEntityEvent<>)))
|
|
|
{
|
|
|
- case EventType.AfterSave:
|
|
|
- RemoveEntityEvent(ev);
|
|
|
- break;
|
|
|
+ AddEntityEvent(ev);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ AddGenericEvent(ev);
|
|
|
}
|
|
|
}
|
|
|
- public static void AddEvent(IProvider provider, Event ev)
|
|
|
+ public static void RemoveEvent(IProvider provider, Event ev)
|
|
|
{
|
|
|
if (!_loadedCache) ReloadCache(provider);
|
|
|
|
|
|
- switch (ev.EventType)
|
|
|
+ var type = GetEventType(ev.EventType);
|
|
|
+ if (type.HasInterface(typeof(IEntityEvent<>)))
|
|
|
{
|
|
|
- case EventType.AfterSave:
|
|
|
- AddEntityEvent(ev);
|
|
|
- break;
|
|
|
+ RemoveEntityEvent(ev);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ RemoveGenericEvent(ev);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -344,6 +395,138 @@ public static class EventUtils
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ 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();
|
|
|
+ eventData.Event.Init(store, eventData, model);
|
|
|
+ Run(store, ev.Event, eventData, model);
|
|
|
+
|
|
|
+ ev.Event.Data = Serialize(eventData);
|
|
|
+ store.Provider.Save(ev.Event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
#endregion
|
|
|
}
|
|
|
|
|
@@ -530,6 +713,15 @@ public interface IPropertiesEvent<TProperties>
|
|
|
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
|
|
|
{
|
|
|
void Init(IStore store, IEventData data, TDataModel model);
|