mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Merge branch 'master' into RGB.NET_update
This commit is contained in:
commit
da90c0d3a1
94 files changed
+1525
-1007
No files matched your search
@@ -68,6 +68,7 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Ccontrollers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cendpoints/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cendpoints_005Ceventargs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cjson/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -36,9 +36,6 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public override void ApplyValue(float value)
|
||||
{
|
||||
if (ValueTypeSetExpression == null)
|
||||
return;
|
||||
|
||||
if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
|
||||
value = Math.Min(value, max);
|
||||
if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
internal BoolLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
RegisterDataBindingProperty(b => b, new GeneralDataBindingConverter<bool>());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
namespace Artemis.Core
|
||||
using System.ComponentModel;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
||||
{
|
||||
private ColorGradient? _subscribedGradient;
|
||||
|
||||
internal ColorGradientLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBindingsSupported = false;
|
||||
DataBindingsSupported = true;
|
||||
DefaultValue = new ColorGradient();
|
||||
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
private void CreateDataBindingRegistrations()
|
||||
{
|
||||
ClearDataBindingProperties();
|
||||
if (CurrentValue == null)
|
||||
return;
|
||||
|
||||
for (int index = 0; index < CurrentValue.Stops.Count; index++)
|
||||
{
|
||||
int stopIndex = index;
|
||||
|
||||
void Setter(SKColor value)
|
||||
{
|
||||
CurrentValue.Stops[stopIndex].Color = value;
|
||||
CurrentValue.OnColorValuesUpdated();
|
||||
}
|
||||
|
||||
RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
|
||||
/// </summary>
|
||||
@@ -31,6 +57,22 @@
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
|
||||
if (_subscribedGradient != BaseValue)
|
||||
{
|
||||
if (_subscribedGradient != null)
|
||||
_subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient = BaseValue;
|
||||
_subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged;
|
||||
}
|
||||
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
|
||||
private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count)
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
|
||||
#region Overrides of LayerProperty<ColorGradient>
|
||||
@@ -41,10 +83,31 @@
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
|
||||
|
||||
base.OnInitialize();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class ColorStopDataBindingConverter : DataBindingConverter<ColorGradient, SKColor>
|
||||
{
|
||||
public ColorStopDataBindingConverter()
|
||||
{
|
||||
SupportsInterpolate = true;
|
||||
SupportsSum = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SKColor Sum(SKColor a, SKColor b)
|
||||
{
|
||||
return a.Sum(b);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
|
||||
{
|
||||
return a.Interpolate(b, (float) progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
internal FloatLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(value => value, new FloatDataBindingConverter());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
{
|
||||
internal FloatRangeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter<FloatRange>());
|
||||
RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter<FloatRange>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter<FloatRange>(), "Start");
|
||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Artemis.Core
|
||||
{
|
||||
internal IntLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(value => value, new IntDataBindingConverter());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
{
|
||||
internal IntRangeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(range => range.Start, new IntDataBindingConverter<IntRange>());
|
||||
RegisterDataBindingProperty(range => range.End, new IntDataBindingConverter<IntRange>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter<IntRange>(), "Start");
|
||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(value => value, new SKColorDataBindingConverter());
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter<SKPoint>());
|
||||
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter<SKPoint>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter<SKPoint>(), "X");
|
||||
RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter<SKPoint>(), "Y");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter<SKSize>());
|
||||
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>());
|
||||
RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter<SKSize>(), "Width");
|
||||
RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter<SKSize>(), "Height");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> 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;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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)
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public Type? GetTargetType()
|
||||
{
|
||||
return Registration?.PropertyExpression.ReturnType;
|
||||
return Registration?.Getter.Method.ReturnType;
|
||||
}
|
||||
|
||||
private void ResetEasing(TProperty value)
|
||||
@@ -272,7 +272,7 @@ namespace Artemis.Core
|
||||
throw new ObjectDisposedException("DataBinding");
|
||||
|
||||
// General
|
||||
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression);
|
||||
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
|
||||
if (registration != null)
|
||||
ApplyRegistration(registration);
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace Artemis.Core
|
||||
|
||||
// Don't save an invalid state
|
||||
if (Registration != null)
|
||||
Entity.TargetExpression = Registration.PropertyExpression.ToString();
|
||||
Entity.Identifier = Registration.DisplayName;
|
||||
|
||||
Entity.EasingTime = EasingTime;
|
||||
Entity.EasingFunction = (int) EasingFunction;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
@@ -10,21 +8,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a dynamically compiled getter pointing to the data bound property
|
||||
/// </summary>
|
||||
public Func<TLayerProperty, TProperty>? GetExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dynamically compiled setter pointing to the data bound property used for value types
|
||||
/// </summary>
|
||||
public Action<TProperty>? ValueTypeSetExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dynamically compiled setter pointing to the data bound property used for reference types
|
||||
/// </summary>
|
||||
public Action<TLayerProperty, TProperty>? ReferenceTypeSetExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding this converter is applied to
|
||||
/// </summary>
|
||||
@@ -40,9 +23,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public bool SupportsInterpolate { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type SupportedType => typeof(TProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
|
||||
/// </summary>
|
||||
@@ -65,12 +45,9 @@ namespace Artemis.Core
|
||||
/// <param name="value"></param>
|
||||
public virtual void ApplyValue(TProperty value)
|
||||
{
|
||||
if (DataBinding == null)
|
||||
if (DataBinding?.Registration == null)
|
||||
throw new ArtemisCoreException("Data binding converter is not yet initialized");
|
||||
if (ReferenceTypeSetExpression != null)
|
||||
ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value);
|
||||
else if (ValueTypeSetExpression != null)
|
||||
ValueTypeSetExpression(value);
|
||||
DataBinding.Registration.Setter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,9 +55,9 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public virtual TProperty GetValue()
|
||||
{
|
||||
if (DataBinding == null || GetExpression == null)
|
||||
if (DataBinding?.Registration == null)
|
||||
throw new ArtemisCoreException("Data binding converter is not yet initialized");
|
||||
return GetExpression(DataBinding.LayerProperty.CurrentValue);
|
||||
return DataBinding.Registration.Getter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,83 +81,10 @@ namespace Artemis.Core
|
||||
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
|
||||
|
||||
DataBinding = dataBinding;
|
||||
GetExpression = dataBinding.Registration.PropertyExpression.Compile();
|
||||
CreateSetExpression();
|
||||
|
||||
OnInitialized();
|
||||
}
|
||||
|
||||
private void CreateSetExpression()
|
||||
{
|
||||
// If the registration does not point towards a member of LayerProperty<T>.CurrentValue, assign directly to LayerProperty<T>.CurrentValue
|
||||
if (DataBinding!.Registration?.Member == null)
|
||||
{
|
||||
CreateSetCurrentValueExpression();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the member of LayerProperty<T>.CurrentValue has a setter
|
||||
MethodInfo? setterMethod = null;
|
||||
if (DataBinding.Registration.Member is PropertyInfo propertyInfo)
|
||||
setterMethod = propertyInfo.GetSetMethod();
|
||||
// If there is no setter, the built-in data binding cannot do its job, stay null
|
||||
if (setterMethod == null)
|
||||
return;
|
||||
|
||||
// If LayerProperty<T>.CurrentValue is a value type, assign it directly to LayerProperty<T>.CurrentValue after applying the changes
|
||||
if (typeof(TLayerProperty).IsValueType)
|
||||
CreateSetValueTypeExpression();
|
||||
// If it is a reference type it can safely be updated by its reference
|
||||
else
|
||||
CreateSetReferenceTypeExpression();
|
||||
}
|
||||
|
||||
private void CreateSetReferenceTypeExpression()
|
||||
{
|
||||
if (DataBinding!.Registration?.Member == null)
|
||||
throw new ArtemisCoreException("Cannot create value setter for data binding without a registration");
|
||||
|
||||
ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
|
||||
ParameterExpression parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue");
|
||||
MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member);
|
||||
BinaryExpression assignment = Expression.Assign(memberAccess, propertyValue);
|
||||
Expression<Action<TLayerProperty, TProperty>> referenceTypeLambda = Expression.Lambda<Action<TLayerProperty, TProperty>>(assignment, parameter, propertyValue);
|
||||
|
||||
ReferenceTypeSetExpression = referenceTypeLambda.Compile();
|
||||
}
|
||||
|
||||
private void CreateSetValueTypeExpression()
|
||||
{
|
||||
if (DataBinding!.Registration?.Member == null)
|
||||
throw new ArtemisCoreException("Cannot create value setter for data binding without a registration");
|
||||
|
||||
ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
|
||||
ParameterExpression variableCurrent = Expression.Variable(typeof(TLayerProperty), "current");
|
||||
ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty);
|
||||
MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty,
|
||||
DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]);
|
||||
|
||||
BlockExpression body = Expression.Block(
|
||||
new[] {variableCurrent},
|
||||
Expression.Assign(variableCurrent, layerPropertyMemberAccess),
|
||||
Expression.Assign(Expression.MakeMemberAccess(variableCurrent, DataBinding.Registration.Member), propertyValue),
|
||||
Expression.Assign(layerPropertyMemberAccess, variableCurrent)
|
||||
);
|
||||
|
||||
Expression<Action<TProperty>> valueTypeLambda = Expression.Lambda<Action<TProperty>>(body, propertyValue);
|
||||
ValueTypeSetExpression = valueTypeLambda.Compile();
|
||||
}
|
||||
|
||||
private void CreateSetCurrentValueExpression()
|
||||
{
|
||||
ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue");
|
||||
ConstantExpression layerProperty = Expression.Constant(DataBinding!.LayerProperty);
|
||||
MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty,
|
||||
DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]);
|
||||
|
||||
BinaryExpression body = Expression.Assign(layerPropertyMemberAccess, propertyValue);
|
||||
Expression<Action<TProperty>> lambda = Expression.Lambda<Action<TProperty>>(body, propertyValue);
|
||||
ValueTypeSetExpression = lambda.Compile();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public Type SupportedType => typeof(TProperty);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
|
||||
namespace Artemis.Core
|
||||
@@ -9,16 +7,14 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
|
||||
{
|
||||
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty,
|
||||
DataBindingConverter<TLayerProperty, TProperty> converter,
|
||||
Expression<Func<TLayerProperty, TProperty>> propertyExpression)
|
||||
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, DataBindingConverter<TLayerProperty, TProperty> converter,
|
||||
Func<TProperty> getter, Action<TProperty> setter, string displayName)
|
||||
{
|
||||
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
|
||||
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
|
||||
PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression));
|
||||
|
||||
if (propertyExpression.Body is MemberExpression memberExpression)
|
||||
Member = memberExpression.Member;
|
||||
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
|
||||
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
|
||||
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,15 +28,17 @@ namespace Artemis.Core
|
||||
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expression that that accesses the property
|
||||
/// Gets the function to call to get the value of the property
|
||||
/// </summary>
|
||||
public Expression<Func<TLayerProperty, TProperty>> PropertyExpression { get; }
|
||||
public Func<TProperty> Getter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member the <see cref="PropertyExpression" /> targets
|
||||
/// <para><see langword="null"/> if the <see cref="PropertyExpression" /> is not a member expression</para>
|
||||
/// Gets the action to call to set the value of the property
|
||||
/// </summary>
|
||||
public MemberInfo? Member { get; }
|
||||
public Action<TProperty> Setter { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding created using this registration
|
||||
@@ -59,7 +57,7 @@ namespace Artemis.Core
|
||||
if (DataBinding != null)
|
||||
return DataBinding;
|
||||
|
||||
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString());
|
||||
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName);
|
||||
if (dataBinding == null)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
/// </summary>
|
||||
public interface IDataBindingRegistration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the display name of the data binding registration
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data binding applied using this registration
|
||||
/// </summary>
|
||||
|
||||
@@ -213,9 +213,25 @@ namespace Artemis.Core
|
||||
Expression? expression = Expression.Convert(parameter, Target.GetType());
|
||||
Expression? nullCondition = null;
|
||||
|
||||
MethodInfo equals = typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public)!;
|
||||
foreach (DataModelPathSegment segment in _segments)
|
||||
{
|
||||
BinaryExpression notNull = Expression.NotEqual(expression, Expression.Default(expression.Type));
|
||||
BinaryExpression notNull;
|
||||
try
|
||||
{
|
||||
notNull = Expression.NotEqual(expression, Expression.Default(expression.Type));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
notNull = Expression.NotEqual(
|
||||
Expression.Call(
|
||||
null,
|
||||
equals,
|
||||
Expression.Convert(expression, typeof(object)),
|
||||
Expression.Convert(Expression.Default(expression.Type), typeof(object))),
|
||||
Expression.Constant(true));
|
||||
}
|
||||
|
||||
nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull;
|
||||
expression = segment.Initialize(parameter, expression, nullCondition);
|
||||
if (expression == null)
|
||||
|
||||
@@ -97,6 +97,16 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a data binding property has been added
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when all data binding properties have been removed
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a data binding has been enabled
|
||||
/// </summary>
|
||||
|
||||
@@ -379,21 +379,16 @@ namespace Artemis.Core
|
||||
public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a data binding registration by the expression used to register it
|
||||
/// Gets a data binding registration by the display name used to register it
|
||||
/// <para>Note: The expression must exactly match the one used to register the data binding</para>
|
||||
/// </summary>
|
||||
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
|
||||
{
|
||||
return GetDataBindingRegistration<TProperty>(propertyExpression.ToString());
|
||||
}
|
||||
|
||||
internal DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string expression)
|
||||
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string identifier)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
|
||||
registration.PropertyExpression.ToString() == expression);
|
||||
registration.DisplayName == identifier);
|
||||
return (DataBindingRegistration<T, TProperty>?) match;
|
||||
}
|
||||
|
||||
@@ -410,21 +405,35 @@ namespace Artemis.Core
|
||||
/// Registers a data binding property so that is available to the data binding system
|
||||
/// </summary>
|
||||
/// <typeparam name="TProperty">The type of the layer property</typeparam>
|
||||
/// <param name="propertyExpression">The expression pointing to the value to register</param>
|
||||
/// <param name="getter">The function to call to get the value of the property</param>
|
||||
/// <param name="setter">The action to call to set the value of the property</param>
|
||||
/// <param name="converter">The converter to use while applying the data binding</param>
|
||||
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter)
|
||||
/// <param name="displayName">The display name of the data binding property</param>
|
||||
public DataBindingRegistration<T, TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty> setter, DataBindingConverter<T, TProperty> converter,
|
||||
string displayName)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName))
|
||||
throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered.");
|
||||
|
||||
DataBindingRegistration<T, TProperty> registration = new(this, converter, getter, setter, displayName);
|
||||
_dataBindingRegistrations.Add(registration);
|
||||
|
||||
OnDataBindingPropertyRegistered();
|
||||
return registration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all data binding properties so they are no longer available to the data binding system
|
||||
/// </summary>
|
||||
public void ClearDataBindingProperties()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("LayerProperty");
|
||||
|
||||
if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter)
|
||||
throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'");
|
||||
|
||||
if (converter.SupportedType != propertyExpression.ReturnType)
|
||||
throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " +
|
||||
"because the provided converter does not support the property's type");
|
||||
|
||||
_dataBindingRegistrations.Add(new DataBindingRegistration<T, TProperty>(this, converter, propertyExpression));
|
||||
_dataBindingRegistrations.Clear();
|
||||
OnDataBindingPropertiesCleared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -661,6 +670,12 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
|
||||
|
||||
@@ -716,6 +731,22 @@ namespace Artemis.Core
|
||||
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingPropertyRegistered()
|
||||
{
|
||||
DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingDisabled" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDataBindingPropertiesCleared()
|
||||
{
|
||||
DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataBindingEnabled" /> event
|
||||
/// </summary>
|
||||
|
||||
@@ -51,12 +51,6 @@ namespace Artemis.Core.DeviceProviders
|
||||
/// </summary>
|
||||
public bool CanDetectLogicalLayout { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Disable()
|
||||
{
|
||||
// Does not happen with device providers, they require Artemis to restart
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
|
||||
/// </summary>
|
||||
|
||||
@@ -178,12 +178,15 @@ namespace Artemis.Core
|
||||
{
|
||||
foreach (PluginFeature feature in Features)
|
||||
feature.Dispose();
|
||||
SetEnabled(false);
|
||||
|
||||
Kernel?.Dispose();
|
||||
PluginLoader?.Dispose();
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
_features.Clear();
|
||||
SetEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,10 +73,10 @@ namespace Artemis.Core
|
||||
|
||||
if (!enable)
|
||||
{
|
||||
IsEnabled = false;
|
||||
|
||||
// Even if disable failed, still leave it in a disabled state to avoid more issues
|
||||
InternalDisable();
|
||||
IsEnabled = false;
|
||||
|
||||
OnDisabled();
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +129,8 @@ namespace Artemis.Core
|
||||
|
||||
internal virtual void InternalDisable()
|
||||
{
|
||||
Disable();
|
||||
if (IsEnabled)
|
||||
Disable();
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
@@ -145,7 +146,7 @@ namespace Artemis.Core
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Disable();
|
||||
InternalDisable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ namespace Artemis.Core.Services
|
||||
/// <returns>The resulting plugin</returns>
|
||||
Plugin ImportPlugin(string fileName);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads and permanently removes the provided plugin
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to remove</param>
|
||||
void RemovePlugin(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Enables the provided plugin feature
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.Ninject;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
@@ -275,6 +276,7 @@ namespace Artemis.Core.Services
|
||||
plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure =>
|
||||
{
|
||||
configure.IsUnloadable = true;
|
||||
configure.LoadInMemory = true;
|
||||
configure.PreferSharedTypes = true;
|
||||
});
|
||||
|
||||
@@ -430,7 +432,6 @@ namespace Artemis.Core.Services
|
||||
OnPluginDisabled(new PluginEventArgs(plugin));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Plugin ImportPlugin(string fileName)
|
||||
{
|
||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins"));
|
||||
@@ -449,7 +450,16 @@ namespace Artemis.Core.Services
|
||||
|
||||
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
||||
if (existing != null)
|
||||
throw new ArtemisPluginException($"A plugin with the same GUID is already loaded: {existing.Info}");
|
||||
{
|
||||
try
|
||||
{
|
||||
RemovePlugin(existing);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e);
|
||||
}
|
||||
}
|
||||
|
||||
string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", "");
|
||||
string uniqueTargetDirectory = targetDirectory;
|
||||
@@ -464,19 +474,38 @@ namespace Artemis.Core.Services
|
||||
|
||||
// Extract everything in the same archive directory to the unique plugin directory
|
||||
DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory));
|
||||
Directory.CreateDirectory(directoryInfo.FullName);
|
||||
Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
|
||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
||||
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
|
||||
{
|
||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory))
|
||||
{
|
||||
string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
||||
zipArchiveEntry.ExtractToFile(target);
|
||||
// Create folders
|
||||
if (zipArchiveEntry.FullName.EndsWith("/"))
|
||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
||||
// Extract files
|
||||
else
|
||||
zipArchiveEntry.ExtractToFile(target);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the newly extracted plugin and return the result
|
||||
return LoadPlugin(directoryInfo);
|
||||
}
|
||||
|
||||
public void RemovePlugin(Plugin plugin)
|
||||
{
|
||||
DirectoryInfo directory = plugin.Directory;
|
||||
lock (_plugins)
|
||||
{
|
||||
if (_plugins.Contains(plugin))
|
||||
UnloadPlugin(plugin);
|
||||
}
|
||||
|
||||
directory.Delete(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Features
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Artemis.Core.Services
|
||||
_modifyingProviders = true;
|
||||
|
||||
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
|
||||
Surface.Detach(deviceProvider.Devices);
|
||||
Surface.Detach(toRemove.Select(d => d.RgbDevice));
|
||||
foreach (ArtemisDevice device in toRemove)
|
||||
RemoveDevice(device);
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Artemis.Core.Services
|
||||
_modifyingProviders = true;
|
||||
|
||||
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
|
||||
Surface.Detach(deviceProvider.Devices);
|
||||
Surface.Detach(toRemove.Select(d => d.RgbDevice));
|
||||
foreach (ArtemisDevice device in toRemove)
|
||||
RemoveDevice(device);
|
||||
|
||||
|
||||
@@ -79,6 +79,11 @@ namespace Artemis.Core.Services.Models
|
||||
public void Arrange(List<ArtemisDevice> devices)
|
||||
{
|
||||
ArrangedDevices.Clear();
|
||||
|
||||
// Not much to do here
|
||||
if (!devices.Any())
|
||||
return;
|
||||
|
||||
foreach (ArtemisDevice surfaceDevice in devices)
|
||||
{
|
||||
surfaceDevice.X = 0;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
|
||||
/// <see cref="object" /> or <see langword="null" />.
|
||||
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
|
||||
/// </summary>
|
||||
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
|
||||
{
|
||||
private readonly Module<T>? _module;
|
||||
private readonly DataModelExpansion<T>? _dataModelExpansion;
|
||||
|
||||
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
|
||||
{
|
||||
_module = module ?? throw new ArgumentNullException(nameof(module));
|
||||
|
||||
ThrowOnFail = true;
|
||||
Accepts = MimeType.Json;
|
||||
}
|
||||
|
||||
internal DataModelJsonPluginEndPoint(DataModelExpansion<T> dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule)
|
||||
{
|
||||
_dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion));
|
||||
|
||||
ThrowOnFail = true;
|
||||
Accepts = MimeType.Json;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
|
||||
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
|
||||
/// JSON throws a <see cref="JsonException" />.
|
||||
/// </summary>
|
||||
public bool ThrowOnFail { get; set; }
|
||||
|
||||
#region Overrides of PluginEndPoint
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about endpoint exception related events
|
||||
/// </summary>
|
||||
public class EndpointExceptionEventArgs : EventArgs
|
||||
{
|
||||
internal EndpointExceptionEventArgs(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception that occurred
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using EmbedIO;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about endpoint request related events
|
||||
/// </summary>
|
||||
public class EndpointRequestEventArgs : EventArgs
|
||||
{
|
||||
internal EndpointRequestEventArgs(IHttpContext context)
|
||||
{
|
||||
Context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP context of the request
|
||||
/// </summary>
|
||||
public IHttpContext Context { get; }
|
||||
}
|
||||
}
|
||||
@@ -52,15 +52,65 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
public string? Returns { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a request threw an unhandled exception
|
||||
/// </summary>
|
||||
public event EventHandler<EndpointExceptionEventArgs>? RequestException;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a request is about to be processed
|
||||
/// </summary>
|
||||
public event EventHandler<EndpointRequestEventArgs>? ProcessingRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a request was processed
|
||||
/// </summary>
|
||||
public event EventHandler<EndpointRequestEventArgs>? ProcessedRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the end point has to process a request
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context of the request</param>
|
||||
protected abstract Task ProcessRequest(IHttpContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="RequestException" /> event
|
||||
/// </summary>
|
||||
/// <param name="e">The exception that occurred during the request</param>
|
||||
protected virtual void OnRequestException(Exception e)
|
||||
{
|
||||
RequestException?.Invoke(this, new EndpointExceptionEventArgs(e));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ProcessingRequest" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnProcessingRequest(IHttpContext context)
|
||||
{
|
||||
ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ProcessedRequest" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnProcessedRequest(IHttpContext context)
|
||||
{
|
||||
ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context));
|
||||
}
|
||||
|
||||
internal async Task InternalProcessRequest(IHttpContext context)
|
||||
{
|
||||
await ProcessRequest(context);
|
||||
try
|
||||
{
|
||||
OnProcessingRequest(context);
|
||||
await ProcessRequest(context);
|
||||
OnProcessedRequest(context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnRequestException(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisabled(object? sender, EventArgs e)
|
||||
|
||||
@@ -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
|
||||
/// <returns>The resulting end point</returns>
|
||||
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
||||
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
|
||||
/// <param name="endPointName">The name of the end point, must be unique</param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="dataModelExpansion" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
||||
/// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
|
||||
/// <param name="endPointName">The name of the end point, must be unique</param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
@@ -67,6 +64,12 @@ namespace Artemis.Core.Services
|
||||
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
|
||||
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}.");
|
||||
|
||||
// If Accept-Charset contains a wildcard, remove the header so we default to UTF8
|
||||
// This is a workaround for an EmbedIO ehh issue
|
||||
string? acceptCharset = context.Request.Headers["Accept-Charset"];
|
||||
if (acceptCharset != null && acceptCharset.Contains("*"))
|
||||
context.Request.Headers.Remove("Accept-Charset");
|
||||
|
||||
// It is up to the registration how the request is eventually handled, it might even set a response here
|
||||
await endPoint.InternalProcessRequest(context);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -28,7 +30,6 @@ namespace Artemis.Core.Services
|
||||
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
|
||||
|
||||
PluginsModule = new PluginsModule("/plugins");
|
||||
|
||||
StartWebServer();
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ namespace Artemis.Core.Services
|
||||
Server?.Dispose();
|
||||
Server = null;
|
||||
|
||||
string url = $"http://localhost:{_webServerPortSetting.Value}/";
|
||||
string url = $"http://*:{_webServerPortSetting.Value}/";
|
||||
WebApiModule apiModule = new("/api/", JsonNetSerializer);
|
||||
PluginsModule.ServerUrl = url;
|
||||
WebServer server = new WebServer(o => o.WithUrlPrefix(url).WithMode(HttpListenerMode.EmbedIO))
|
||||
@@ -126,6 +127,29 @@ namespace Artemis.Core.Services
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel
|
||||
{
|
||||
if (module == null) throw new ArgumentNullException(nameof(module));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
DataModelJsonPluginEndPoint<T> endPoint = new(module, endPointName, PluginsModule);
|
||||
PluginsModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel
|
||||
{
|
||||
if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
DataModelJsonPluginEndPoint<T> endPoint = new(dataModelExpansion, endPointName, PluginsModule);
|
||||
PluginsModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void RemovePluginEndPoint(PluginEndPoint endPoint)
|
||||
{
|
||||
PluginsModule.RemovePluginEndPoint(endPoint);
|
||||
|
||||
Reference in new issue
Block a user