using System.Collections.Concurrent; using InABox.Clients; using InABox.Core; namespace InABox.IPC { public class RPCClient : BaseClient where TEntity : Entity, new() { private IRPCClientTransport _transport; private ConcurrentDictionary Events = new(); private ConcurrentDictionary Responses = new(); private const int DefaultRequestTimeout = 5 * 60 * 1000; // 5 minutes public delegate void ConnectEvent(); public delegate void DisconnectEvent(); public bool Disconnected { get; private set; } public event ConnectEvent? OnConnect; public event DisconnectEvent? OnDisconnect; public RPCClient(Func transport) { _transport = transport(); _transport.OnOpen += Transport_Opened; _transport.OnClose += Transport_Closed; _transport.OnException += Transport_Exception; _transport.OnMessage += Transport_Message; _transport.Connect(); } ~RPCClient() { _transport.OnOpen -= Transport_Opened; _transport.OnClose -= Transport_Closed; _transport.OnException -= Transport_Exception; _transport.OnMessage -= Transport_Message; } #region TransportManagement private void Transport_Opened(IRPCTransport transport, RPCTransportOpenArgs e) { Logger.Send(LogType.Information, "", $"Client Connected"); Disconnected = false; OnConnect?.Invoke(); } private void Transport_Message(IRPCTransport transport, RPCTransportMessageArgs e) { Logger.Send(LogType.Error, "", $"Message received: ({e.Message.Command}) -> {e.Message.Payload}"); } private void Transport_Exception(IRPCTransport transport, RPCTransportExceptionArgs e) { Logger.Send(LogType.Error, "", $"Exception occured: {e.Exception.Message}"); } private void Transport_Closed(IRPCTransport transport, RPCTransportCloseArgs e) { Logger.Send(LogType.Information, "", $"Client Disconnected"); Disconnected = true; OnDisconnect?.Invoke(); } public TResult Send(TParameters properties) where TCommand : IRPCCommand { var request = new RPCMessage( new Guid(), typeof(TCommand).Name, Serialization.Serialize(properties) ); var response = Send(request); if (response.Error != RPCError.NONE) throw new Exception($"Exception in {typeof(TCommand).Name}({request.ID}): {response.Error}"); var result = Serialization.Deserialize(response.Payload); if (result == null) throw new Exception($"{typeof(TCommand).Name}({request.ID}) returned NULL"); return result; } public RPCMessage Send(RPCMessage request, int timeout = DefaultRequestTimeout) { var start = DateTime.Now; var ev = Queue(request.ID); _transport.Send(request); var result = GetResult(request.ID, ev, timeout); return result; } public ManualResetEventSlim Queue(Guid id) { var ev = new ManualResetEventSlim(); Events[id] = ev; return ev; } public RPCMessage GetResult(Guid id, ManualResetEventSlim ev, int timeout) { if (Responses.TryGetValue(id, out var result)) { Responses.Remove(id, out result); Events.Remove(id, out ev); return result; } try { if (!ev.Wait(timeout)) { return new RPCMessage(id,"","",RPCError.TIMEOUT); } } catch (Exception e) { Logger.Send(LogType.Error, "", e.Message); throw; } Responses.Remove(id, out result); Events.Remove(id, out ev); return result ?? new RPCMessage(id,"","",RPCError.UNKNOWN); } #endregion #region Client Interface public override DatabaseInfo Info() { var result = _transport.Send(new RPCInfoParameters()); return result.Info; } private static string[]? _types; public override string[] SupportedTypes() { _types ??= CoreUtils.Entities .Where(x => x.GetInterfaces().Contains(typeof(IPersistent))) .Select(x => x.EntityName().Replace(".", "_")) .ToArray(); return _types; } protected override ValidationData DoValidate(string userid, string password, Guid session = default) { throw new NotImplementedException(); } protected override ValidationData DoValidate(string pin, Guid session = default) { throw new NotImplementedException(); } protected override ValidationData DoValidate(Guid session = default) { throw new NotImplementedException(); } protected override CoreTable DoQuery(Filter? filter, Columns? columns, SortOrder? sort = null) { throw new NotImplementedException(); } protected override TEntity[] DoLoad(Filter? filter = null, SortOrder? sort = null) { throw new NotImplementedException(); } protected override void DoSave(TEntity entity, string auditnote) { throw new NotImplementedException(); } protected override void DoSave(IEnumerable entities, string auditnote) { throw new NotImplementedException(); } protected override void DoDelete(TEntity entity, string auditnote) { throw new NotImplementedException(); } protected override void DoDelete(IList entities, string auditnote) { throw new NotImplementedException(); } protected override Dictionary DoQueryMultiple(Dictionary queries) { throw new NotImplementedException(); } protected override bool DoCheck2FA(string code, Guid? session) { throw new NotImplementedException(); } protected override bool DoPing() { throw new NotImplementedException(); } #endregion } }