diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 7d6c52363..5a6e7bef3 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -68,6 +68,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs index 49a2a0072..8ae6d5411 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs @@ -11,9 +11,8 @@ namespace Artemis.Core public class DataModelConditionEvent : DataModelConditionPart { private bool _disposed; - private IDataModelEvent? _event; - private bool _eventTriggered; private bool _reinitializing; + private DateTime _lastTrigger; /// /// Creates a new instance of the class @@ -53,22 +52,21 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataModelConditionEvent"); - // Ensure the event has not been replaced - if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent) - SubscribeToDataModelEvent(dataModelEvent); - - // Only evaluate to true once every time the event has been triggered - if (!_eventTriggered) + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + return false; + // Only evaluate to true once every time the event has been triggered since the last evaluation + if (dataModelEvent.LastTrigger <= _lastTrigger) return false; - _eventTriggered = false; + _lastTrigger = DateTime.Now; // If there is a child (root group), it must evaluate to true whenever the event triggered if (Children.Any()) - return Children[0].EvaluateObject(_event?.LastEventArgumentsUntyped); + return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped); // If there are no children, we always evaluate to true whenever the event triggered return true; + } /// @@ -132,7 +130,7 @@ namespace Artemis.Core // Target list EventPath?.Save(); Entity.EventPath = EventPath?.Entity; - + // Children Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); @@ -172,16 +170,9 @@ namespace Artemis.Core Entity.Children.Clear(); AddChild(new DataModelConditionGroup(this)); } - } - private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent) - { - if (_event != null) - _event.EventTriggered -= OnEventTriggered; - - _event = dataModelEvent; - if (_event != null) - _event.EventTriggered += OnEventTriggered; + if (EventPath?.GetValue() is IDataModelEvent dataModelEvent) + _lastTrigger = dataModelEvent.LastTrigger; } private Type? GetEventArgumentType() @@ -212,11 +203,6 @@ namespace Artemis.Core #region Event handlers - private void OnEventTriggered(object? sender, EventArgs e) - { - _eventTriggered = true; - } - private void EventPathOnPathValidated(object? sender, EventArgs e) { if (_reinitializing) diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs new file mode 100644 index 000000000..30532aa2b --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; +using EmbedIO; +using Newtonsoft.Json; + +namespace Artemis.Core.Services +{ + /// + /// Represents a plugin web endpoint receiving an object of type and returning any + /// or . + /// Note: Both will be deserialized and serialized respectively using JSON. + /// + public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel + { + private readonly Module? _module; + private readonly DataModelExpansion? _dataModelExpansion; + + internal DataModelJsonPluginEndPoint(Module module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule) + { + _module = module ?? throw new ArgumentNullException(nameof(module)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + internal DataModelJsonPluginEndPoint(DataModelExpansion dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule) + { + _dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + + /// + /// Whether or not the end point should throw an exception if deserializing the received JSON fails. + /// If set to malformed JSON is silently ignored; if set to malformed + /// JSON throws a . + /// + public bool ThrowOnFail { get; set; } + + #region Overrides of PluginEndPoint + + /// + protected override async Task ProcessRequest(IHttpContext context) + { + if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put) + throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls"); + + context.Response.ContentType = MimeType.Json; + + using TextReader reader = context.OpenRequestText(); + try + { + if (_module != null) + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel); + else + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel); + } + catch (JsonException) + { + if (ThrowOnFail) + throw; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/EndpointExceptionEventArgs.cs b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs similarity index 100% rename from src/Artemis.Core/Services/WebServer/EndPoints/EndpointExceptionEventArgs.cs rename to src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointExceptionEventArgs.cs diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs new file mode 100644 index 000000000..6b483cc06 --- /dev/null +++ b/src/Artemis.Core/Services/WebServer/EndPoints/EventArgs/EndpointRequestEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using EmbedIO; + +namespace Artemis.Core.Services +{ + /// + /// Provides data about endpoint request related events + /// + public class EndpointRequestEventArgs : EventArgs + { + internal EndpointRequestEventArgs(IHttpContext context) + { + Context = context; + } + + /// + /// Gets the HTTP context of the request + /// + public IHttpContext Context { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs index 504fd0eff..c62c41fe3 100644 --- a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs +++ b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs @@ -57,6 +57,16 @@ namespace Artemis.Core.Services /// public event EventHandler? RequestException; + /// + /// Occurs whenever a request is about to be processed + /// + public event EventHandler? ProcessingRequest; + + /// + /// Occurs whenever a request was processed + /// + public event EventHandler? ProcessedRequest; + /// /// Called whenever the end point has to process a request /// @@ -72,11 +82,29 @@ namespace Artemis.Core.Services RequestException?.Invoke(this, new EndpointExceptionEventArgs(e)); } + /// + /// Invokes the event + /// + protected virtual void OnProcessingRequest(IHttpContext context) + { + ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context)); + } + + /// + /// Invokes the event + /// + protected virtual void OnProcessedRequest(IHttpContext context) + { + ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context)); + } + internal async Task InternalProcessRequest(IHttpContext context) { try { + OnProcessingRequest(context); await ProcessRequest(context); + OnProcessedRequest(context); } catch (Exception e) { diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 704a4a19c..ef98d3fa0 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; @@ -43,6 +45,24 @@ namespace Artemis.Core.Services /// The resulting end point JsonPluginEndPoint AddResponsiveJsonEndPoint(PluginFeature feature, string endPointName, Func requestHandler); + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The module whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel; + + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The data model expansion whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel; + /// /// Adds a new endpoint for the given plugin feature receiving an a . /// diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index e19e6a4a9..8de5ce9e4 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; using Newtonsoft.Json; @@ -125,6 +127,29 @@ namespace Artemis.Core.Services return endPoint; } + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel + { + if (module == null) throw new ArgumentNullException(nameof(module)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(module, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel + { + if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(dataModelExpansion, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + + private void HandleDataModelRequest(Module module, T value) where T : DataModel + { + + } + public void RemovePluginEndPoint(PluginEndPoint endPoint) { PluginsModule.RemovePluginEndPoint(endPoint); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index cc49f3d59..05c33df00 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -200,7 +200,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}"> RESTART @@ -215,7 +215,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IGNORE @@ -230,7 +230,7 @@ + IsChecked="{Binding Path=EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> COPY diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index a04d3d563..3f79b19e3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -38,7 +38,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public RenderProfileElement RenderProfileElement { get => _renderProfileElement; - set => SetAndNotify(ref _renderProfileElement, value); + set + { + if (!SetAndNotify(ref _renderProfileElement, value)) return; + NotifyOfPropertyChange(nameof(DisplayContinuously)); + NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); + NotifyOfPropertyChange(nameof(EventOverlapMode)); + } } public bool DisplayContinuously @@ -65,6 +71,17 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions } } + public TimeLineEventOverlapMode EventOverlapMode + { + get => RenderProfileElement?.Timeline.EventOverlapMode ?? TimeLineEventOverlapMode.Restart; + set + { + if (RenderProfileElement == null || RenderProfileElement?.Timeline.EventOverlapMode == value) return; + RenderProfileElement.Timeline.EventOverlapMode = value; + _profileEditorService.UpdateSelectedProfileElement(); + } + } + public bool ConditionBehaviourEnabled => RenderProfileElement != null; protected override void OnInitialActivate() @@ -119,5 +136,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); } + + public void EventTriggerModeSelected() + { + _profileEditorService.UpdateSelectedProfileElement(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index e2b83d7d0..0e0e97656 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -127,7 +127,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs lock (MainDataModel) { - MainDataModel.Update(_dataModelUIService, null); + MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true)); } }