235 lines
9.9 KiB
C#
235 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using ln.http.websocket;
|
|
using ln.json;
|
|
using ln.json.mapping;
|
|
using ln.logging;
|
|
using ln.type;
|
|
|
|
namespace ln.http.api
|
|
{
|
|
public class JSONEventWebSocketResponse : JSONWebSocketResponse, IDisposable
|
|
{
|
|
static List<JSONEventWebSocketResponse> activeWebSockets = new List<JSONEventWebSocketResponse>();
|
|
public static void SendUpdates()
|
|
{
|
|
if (Monitor.TryEnter(activeWebSockets, 100))
|
|
{
|
|
try
|
|
{
|
|
foreach (JSONEventWebSocketResponse websocket in activeWebSockets)
|
|
websocket.SendUpdate();
|
|
} catch (Exception e)
|
|
{
|
|
Logging.Log(e);
|
|
} finally
|
|
{
|
|
Monitor.Exit(activeWebSockets);
|
|
}
|
|
} else {
|
|
Logging.Log(LogLevel.WARNING, "JSONEventWebSocketResponse: SendUpdates(): failed to enter critical section");
|
|
}
|
|
}
|
|
|
|
public string DefaultTargetName { get; set; }
|
|
|
|
Dictionary<string, Target> publishedTargets = new Dictionary<string, Target>();
|
|
|
|
|
|
public JSONEventWebSocketResponse()
|
|
{
|
|
OnWebSocketReceivedJSON += JSONReceived;
|
|
OnWebSocketStateChanged += (WebSocketResponse websocket, WebSocketState newState) => {
|
|
switch (newState)
|
|
{
|
|
case WebSocketState.CLOSED:
|
|
lock (activeWebSockets)
|
|
activeWebSockets.Remove(this);
|
|
break;
|
|
case WebSocketState.OPEN:
|
|
lock (activeWebSockets)
|
|
activeWebSockets.Add(this);
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
|
|
public JSONEventWebSocketResponse(string defaultTargetName)
|
|
:this()
|
|
{
|
|
DefaultTargetName = defaultTargetName;
|
|
}
|
|
|
|
public JSONEventWebSocketResponse(object defaultInstance)
|
|
:this()
|
|
{
|
|
AddTarget(null, defaultInstance, defaultInstance.GetType().GetCustomAttribute<ESTargetAttribute>()?.PublishPublicMembers ?? false, out Target target);
|
|
DefaultTargetName = target.Name;
|
|
}
|
|
public JSONEventWebSocketResponse(object defaultInstance, bool publishPublicMembers)
|
|
:this()
|
|
{
|
|
AddTarget(null, defaultInstance, publishPublicMembers, out Target target);
|
|
DefaultTargetName = target.Name;
|
|
}
|
|
|
|
public void AddTarget(object instance) => AddTarget(null, instance, instance.GetType().GetCustomAttribute<ESTargetAttribute>()?.PublishPublicMembers ?? false);
|
|
public void AddTarget(object instance, bool publishPublicMembers) => AddTarget(null, instance, publishPublicMembers);
|
|
public void AddTarget(string targetName, object instance, bool publishPublicMembers) => AddTarget(targetName, instance, publishPublicMembers, out Target target);
|
|
public void AddTarget(string targetName, object instance, bool publishPublicMembers, out Target target)
|
|
{
|
|
target = new Target(targetName, instance, publishPublicMembers);
|
|
publishedTargets.Add(target.Name, target);
|
|
}
|
|
|
|
|
|
void JSONReceived(JSONWebSocketResponse sender, JSONValue json)
|
|
{
|
|
if (json is JSONObject jsonMessage)
|
|
{
|
|
string eventName = jsonMessage["event"].ToNative().ToString();
|
|
string targetName = jsonMessage["target"].ToNative().ToString();
|
|
JSONValue jsonValue = jsonMessage["value"];
|
|
|
|
Target target = publishedTargets[targetName ?? DefaultTargetName];
|
|
|
|
switch (eventName)
|
|
{
|
|
case "action":
|
|
target.Call(jsonValue);
|
|
break;
|
|
case "set":
|
|
target.SetProperty(jsonValue);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public virtual void SendUpdate()
|
|
{
|
|
JSONObject jsonUpdateValue = new JSONObject();
|
|
JSONObject jsonUpdateMessage = new JSONObject()
|
|
.Add("event", "update")
|
|
.Add("value", jsonUpdateValue);
|
|
|
|
foreach (Target target in publishedTargets.Values)
|
|
jsonUpdateValue.Add(target.Name, target.CreateUpdateValue());
|
|
|
|
Send(jsonUpdateMessage);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
lock (activeWebSockets)
|
|
activeWebSockets.Remove(this);
|
|
}
|
|
|
|
public class Target
|
|
{
|
|
public string Name { get; }
|
|
public object Instance { get; }
|
|
|
|
Dictionary<string,MethodInfo> publishedMethods = new Dictionary<string, MethodInfo>();
|
|
Dictionary<string,PropertyInfo> publishedProperties = new Dictionary<string, PropertyInfo>();
|
|
Dictionary<string,FieldInfo> publishedFields = new Dictionary<string, FieldInfo>();
|
|
|
|
|
|
public Target(object instance, bool publishPublicMembers) :this(instance.GetType().Name, instance, publishPublicMembers) { }
|
|
public Target(object instance) :this(instance.GetType().Name, instance, false) { }
|
|
public Target(string targetName, object instance) : this(targetName, instance, false) { }
|
|
public Target(string targetName, object instance, bool publishPublicMembers)
|
|
{
|
|
Instance = instance;
|
|
Name = targetName ?? Instance.GetType().Name;
|
|
|
|
Initialize(publishPublicMembers);
|
|
}
|
|
|
|
|
|
void Initialize(bool publishPublicMembers)
|
|
{
|
|
foreach (MethodInfo methodInfo in Instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
|
|
{
|
|
ESMethodAttribute methodAttribute = methodInfo.GetCustomAttribute<ESMethodAttribute>();
|
|
if ((methodAttribute != null) || (publishPublicMembers && methodInfo.IsPublic))
|
|
publishedMethods.Add(methodAttribute?.Name ?? methodInfo.Name, methodInfo);
|
|
}
|
|
|
|
foreach (FieldInfo fieldInfo in Instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
|
|
{
|
|
ESPropertyAttribute propertyAttribute = fieldInfo.GetCustomAttribute<ESPropertyAttribute>();
|
|
if ((propertyAttribute != null) || (publishPublicMembers && fieldInfo.IsPublic))
|
|
publishedFields.Add(propertyAttribute?.Name ?? fieldInfo.Name, fieldInfo);
|
|
}
|
|
|
|
foreach (PropertyInfo propertyInfo in Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
|
|
{
|
|
ESPropertyAttribute propertyAttribute = propertyInfo.GetCustomAttribute<ESPropertyAttribute>();
|
|
if ((propertyAttribute != null) || (publishPublicMembers && propertyInfo.GetMethod.IsPublic))
|
|
publishedProperties.Add(propertyAttribute?.Name ?? propertyInfo.Name, propertyInfo);
|
|
}
|
|
}
|
|
|
|
public JSONValue CreateUpdateValue()
|
|
{
|
|
JSONObject jsonUpdate = new JSONObject();
|
|
|
|
foreach (PropertyInfo propertyInfo in publishedProperties.Values)
|
|
if (propertyInfo.CanRead)
|
|
jsonUpdate.Add(propertyInfo.Name, JSONMapper.DefaultMapper.ToJson(propertyInfo.GetValue(Instance)));
|
|
|
|
foreach (FieldInfo fieldInfo in publishedFields.Values)
|
|
jsonUpdate.Add(fieldInfo.Name, JSONMapper.DefaultMapper.ToJson(fieldInfo.GetValue(Instance)));
|
|
|
|
return jsonUpdate;
|
|
}
|
|
|
|
public void Call(JSONValue callValue)
|
|
{
|
|
string methodName;
|
|
MethodInfo methodInfo;
|
|
object[] arguments;
|
|
|
|
if (callValue is JSONString jsonMethodName)
|
|
{
|
|
methodName = jsonMethodName.Value;
|
|
methodInfo = publishedMethods[methodName];
|
|
arguments = new object[0];
|
|
}
|
|
else if (callValue is JSONObject callObject)
|
|
{
|
|
methodName = callObject["method"].ToNative().ToString();
|
|
methodInfo = publishedMethods[methodName];
|
|
if (!JSONMapper.DefaultMapper.MapMethodParameters(methodInfo, callObject["arguments"], out arguments))
|
|
throw new ArgumentException(nameof(callValue));
|
|
}
|
|
else
|
|
throw new ArgumentOutOfRangeException(nameof(callValue));
|
|
|
|
methodInfo.Invoke(Instance, arguments);
|
|
}
|
|
|
|
public void Call(string methodName, object[] arguments)
|
|
{
|
|
MethodInfo methodInfo = publishedMethods[methodName];
|
|
methodInfo.Invoke(Instance, arguments);
|
|
}
|
|
|
|
public void SetProperty(JSONValue jsonValue)
|
|
{
|
|
if (jsonValue is JSONObject setObject)
|
|
{
|
|
string propertyName = setObject["name"].ToNative().ToString();
|
|
if (publishedProperties.TryGetValue(propertyName, out PropertyInfo propertyInfo) && propertyInfo.CanWrite)
|
|
propertyInfo.SetValue(Instance, Cast.To(setObject["value"].ToNative(), propertyInfo.PropertyType));
|
|
else if (publishedFields.TryGetValue(propertyName, out FieldInfo fieldInfo) && !fieldInfo.IsInitOnly)
|
|
fieldInfo.SetValue(Instance, Cast.To(setObject["value"].ToNative(), fieldInfo.FieldType));
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
} |