namespace ElectronNET.API { using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using ElectronNET.Common; public abstract class ApiBase { 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 string SocketEventCompleteSuffix { 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; var eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}{apiBase.SocketEventCompleteSuffix}"); var messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync()); BridgeConnector.Socket.On(eventName, (result) => { BridgeConnector.Socket.Off(eventName); lock (this) { this.tcs?.SetResult(result); this.tcs = null; } }); if (apiBase.Id >= 0) { BridgeConnector.Socket.Emit(messageName, apiBase.Id); } else { BridgeConnector.Socket.Emit(messageName); } System.Threading.Tasks.Task.Delay(ApiBase.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; } } } }