ln.http.api/ln.http.api/JSONEventWebSocketResponse.cs

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));
}
}
}
}
}