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 activeWebSockets = new List(); 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 publishedTargets = new Dictionary(); 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()?.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()?.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 publishedMethods = new Dictionary(); Dictionary publishedProperties = new Dictionary(); Dictionary publishedFields = new Dictionary(); 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(); 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(); 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(); 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)); } } } } }