namespace ElectronNET.API { using ElectronNET.API.Serialization; using ElectronNET.Common; using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading.Tasks; public abstract class ApiBase { protected enum SocketEventNameTypes { DashesLowerFirst, NoDashUpperFirst, } internal const int PropertyTimeout = 1000; private readonly string objectName; private readonly ConcurrentDictionary propertyGetters = new ConcurrentDictionary(); private readonly ConcurrentDictionary propertyEventNames = new ConcurrentDictionary(); private readonly ConcurrentDictionary propertyMessageNames = new ConcurrentDictionary(); private readonly ConcurrentDictionary methodMessageNames = new ConcurrentDictionary(); private readonly object objLock = new object(); public virtual int Id { get { return -1; } // ReSharper disable once ValueParameterNotUsed protected set { } } protected abstract SocketEventNameTypes SocketEventNameType { get; } protected ApiBase() { this.objectName = this.GetType().Name.LowerFirst(); } protected void CallMethod0([CallerMemberName] string callerName = null) { var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s); if (this.Id >= 0) { BridgeConnector.Socket.Emit(messageName, this.Id); } else { BridgeConnector.Socket.Emit(messageName); } } protected void CallMethod1(object val1, [CallerMemberName] string callerName = null) { var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s); if (this.Id >= 0) { BridgeConnector.Socket.Emit(messageName, this.Id, val1); } else { BridgeConnector.Socket.Emit(messageName, val1); } } protected void CallMethod2(object val1, object val2, [CallerMemberName] string callerName = null) { var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s); if (this.Id >= 0) { BridgeConnector.Socket.Emit(messageName, this.Id, val1, val2); } else { BridgeConnector.Socket.Emit(messageName, val1, val2); } } protected void CallMethod3(object val1, object val2, object val3, [CallerMemberName] string callerName = null) { var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s); if (this.Id >= 0) { BridgeConnector.Socket.Emit(messageName, this.Id, val1, val2, val3); } else { BridgeConnector.Socket.Emit(messageName, val1, val2, val3); } } protected Task GetPropertyAsync([CallerMemberName] string callerName = null) { Debug.Assert(callerName != null, nameof(callerName) + " != null"); lock (this.objLock) { return this.propertyGetters.GetOrAdd(callerName, _ => { var getter = new PropertyGetter(this, callerName, PropertyTimeout); getter.Task().ContinueWith(_ => { lock (this.objLock) { return this.propertyGetters.TryRemove(callerName, out var _); } }); return getter; }).Task(); } } internal abstract class PropertyGetter { public abstract Task Task(); } internal class PropertyGetter : PropertyGetter { private readonly Task tcsTask; private TaskCompletionSource tcs; public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs) { this.tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); this.tcsTask = this.tcs.Task; string eventName; switch (apiBase.SocketEventNameType) { case SocketEventNameTypes.DashesLowerFirst: eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}-completed"); break; case SocketEventNameTypes.NoDashUpperFirst: eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}{s.StripAsync()}Completed"); break; default: throw new ArgumentOutOfRangeException(); } var messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync()); BridgeConnector.Socket.On(eventName, (result) => { BridgeConnector.Socket.Off(eventName); lock (this) { try { var value = result.Deserialize(ElectronJson.Options); this.tcs?.SetResult(value); } catch (Exception ex) { this.tcs?.TrySetException(ex); } finally { this.tcs = null; } } }); if (apiBase.Id >= 0) { BridgeConnector.Socket.Emit(messageName, apiBase.Id); } else { BridgeConnector.Socket.Emit(messageName); } System.Threading.Tasks.Task.Delay(PropertyTimeout).ContinueWith(_ => { if (this.tcs != null) { lock (this) { if (this.tcs != null) { var ex = new TimeoutException($"No response after {timeoutMs:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()"); this.tcs.TrySetException(ex); this.tcs = null; } } } }); } public override Task Task() { return this.tcsTask as Task; } } } }