2025-11-07 00:00:53 +01:00
|
|
|
|
namespace ElectronNET.API
|
|
|
|
|
|
{
|
2025-11-09 15:15:52 +01:00
|
|
|
|
using ElectronNET.API.Serialization;
|
2025-11-09 12:05:07 +01:00
|
|
|
|
using ElectronNET.Common;
|
2025-11-07 00:04:10 +01:00
|
|
|
|
using System;
|
2025-11-07 00:00:53 +01:00
|
|
|
|
using System.Collections.Concurrent;
|
2025-11-07 00:04:10 +01:00
|
|
|
|
using System.Diagnostics;
|
2025-11-07 00:00:53 +01:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2025-11-09 12:05:07 +01:00
|
|
|
|
using System.Text.Json;
|
2025-11-07 00:04:10 +01:00
|
|
|
|
using System.Threading.Tasks;
|
2025-11-07 00:00:53 +01:00
|
|
|
|
|
|
|
|
|
|
public abstract class ApiBase
|
|
|
|
|
|
{
|
2025-11-09 02:28:39 +01:00
|
|
|
|
protected enum SocketEventNameTypes
|
|
|
|
|
|
{
|
|
|
|
|
|
DashesLowerFirst,
|
|
|
|
|
|
NoDashUpperFirst,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 00:00:53 +01:00
|
|
|
|
internal const int PropertyTimeout = 1000;
|
|
|
|
|
|
|
|
|
|
|
|
private readonly string objectName;
|
2025-11-07 00:04:10 +01:00
|
|
|
|
private readonly ConcurrentDictionary<string, PropertyGetter> propertyGetters = new ConcurrentDictionary<string, PropertyGetter>();
|
|
|
|
|
|
private readonly ConcurrentDictionary<string, string> propertyEventNames = new ConcurrentDictionary<string, string>();
|
|
|
|
|
|
private readonly ConcurrentDictionary<string, string> propertyMessageNames = new ConcurrentDictionary<string, string>();
|
2025-11-07 00:00:53 +01:00
|
|
|
|
private readonly ConcurrentDictionary<string, string> methodMessageNames = new ConcurrentDictionary<string, string>();
|
2025-11-07 00:04:10 +01:00
|
|
|
|
private readonly object objLock = new object();
|
2025-11-07 00:00:53 +01:00
|
|
|
|
|
|
|
|
|
|
public virtual int Id
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ReSharper disable once ValueParameterNotUsed
|
|
|
|
|
|
protected set
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-09 02:28:39 +01:00
|
|
|
|
protected abstract SocketEventNameTypes SocketEventNameType { get; }
|
2025-11-07 00:00:53 +01:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-07 00:04:10 +01:00
|
|
|
|
|
|
|
|
|
|
protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Assert(callerName != null, nameof(callerName) + " != null");
|
|
|
|
|
|
|
|
|
|
|
|
lock (this.objLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
return this.propertyGetters.GetOrAdd(callerName, _ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var getter = new PropertyGetter<T>(this, callerName, PropertyTimeout);
|
|
|
|
|
|
|
|
|
|
|
|
getter.Task<T>().ContinueWith(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (this.objLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
return this.propertyGetters.TryRemove(callerName, out var _);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return getter;
|
|
|
|
|
|
}).Task<T>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal abstract class PropertyGetter
|
|
|
|
|
|
{
|
|
|
|
|
|
public abstract Task<T> Task<T>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal class PropertyGetter<T> : PropertyGetter
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly Task<T> tcsTask;
|
|
|
|
|
|
private TaskCompletionSource<T> tcs;
|
|
|
|
|
|
|
|
|
|
|
|
public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
|
this.tcsTask = this.tcs.Task;
|
|
|
|
|
|
|
2025-11-09 02:28:39 +01:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 00:04:10 +01:00
|
|
|
|
var messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
|
|
|
|
|
|
|
2025-11-09 12:05:07 +01:00
|
|
|
|
BridgeConnector.Socket.On<JsonElement>(eventName, (result) =>
|
2025-11-07 00:04:10 +01:00
|
|
|
|
{
|
|
|
|
|
|
BridgeConnector.Socket.Off(eventName);
|
|
|
|
|
|
|
|
|
|
|
|
lock (this)
|
|
|
|
|
|
{
|
2025-11-09 12:05:07 +01:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-11-09 15:15:52 +01:00
|
|
|
|
var value = result.Deserialize<T>(ElectronJson.Options);
|
2025-11-09 12:05:07 +01:00
|
|
|
|
this.tcs?.SetResult(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.tcs?.TrySetException(ex);
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
this.tcs = null;
|
|
|
|
|
|
}
|
2025-11-07 00:04:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (apiBase.Id >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
BridgeConnector.Socket.Emit(messageName, apiBase.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
BridgeConnector.Socket.Emit(messageName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-09 12:05:07 +01:00
|
|
|
|
System.Threading.Tasks.Task.Delay(PropertyTimeout).ContinueWith(_ =>
|
2025-11-07 00:04:10 +01:00
|
|
|
|
{
|
|
|
|
|
|
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<T1> Task<T1>()
|
|
|
|
|
|
{
|
|
|
|
|
|
return this.tcsTask as Task<T1>;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-07 00:00:53 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|