Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ env:
jobs:
linux:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4
Expand All @@ -26,6 +27,7 @@ jobs:

windows:
runs-on: windows-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4
Expand Down
222 changes: 194 additions & 28 deletions src/ElectronNET.API/API/ApiBase.cs
Original file line number Diff line number Diff line change
@@ -1,49 +1,64 @@
namespace ElectronNET.API
// ReSharper disable InconsistentNaming
namespace ElectronNET.API
{
using ElectronNET.API.Serialization;
using ElectronNET.Common;
using Common;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
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
protected enum SocketTaskEventNameTypes
{
DashesLowerFirst,
NoDashUpperFirst
}
protected enum SocketTaskMessageNameTypes
{
DashesLowerFirst,
NoDashUpperFirst,
NoDashUpperFirst
}

internal const int PropertyTimeout = 1000;
protected enum SocketEventNameTypes
{
DashedLower,
CamelCase,
}

private const int PropertyTimeout = 1000;

private readonly string objectName;
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>();
private readonly ConcurrentDictionary<string, string> methodMessageNames = new ConcurrentDictionary<string, string>();
private readonly ConcurrentDictionary<string, PropertyGetter> propertyGetters;
private readonly ConcurrentDictionary<string, string> propertyEventNames = new();
private readonly ConcurrentDictionary<string, string> propertyMessageNames = new();
private readonly ConcurrentDictionary<string, string> methodMessageNames = new();
private static readonly ConcurrentDictionary<string, EventContainer> eventContainers = new();
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, PropertyGetter>> AllPropertyGetters = new();

private readonly object objLock = new object();

public virtual int Id
{
get
{
return -1;
}
get => -1;

// ReSharper disable once ValueParameterNotUsed
protected set
{
}
}

protected abstract SocketEventNameTypes SocketEventNameType { get; }
protected abstract SocketTaskEventNameTypes SocketTaskEventNameType { get; }
protected virtual SocketTaskMessageNameTypes SocketTaskMessageNameType => SocketTaskMessageNameTypes.NoDashUpperFirst;
protected virtual SocketEventNameTypes SocketEventNameType => SocketEventNameTypes.DashedLower;

protected ApiBase()
{
this.objectName = this.GetType().Name.LowerFirst();
propertyGetters = AllPropertyGetters.GetOrAdd(objectName, _ => new ConcurrentDictionary<string, PropertyGetter>());
}

protected void CallMethod0([CallerMemberName] string callerName = null)
Expand Down Expand Up @@ -98,15 +113,15 @@ protected void CallMethod3(object val1, object val2, object val3, [CallerMemberN
}
}

protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = null)
protected Task<T> GetPropertyAsync<T>(object arg = null, [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);
var getter = new PropertyGetter<T>(this, callerName, PropertyTimeout, arg);

getter.Task<T>().ContinueWith(_ =>
{
Expand All @@ -120,6 +135,98 @@ protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = nul
}).Task<T>();
}
}

protected void AddEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = EventName(callerName);

var eventKey = EventKey(eventName, id);

lock (objLock)
{
var container = eventContainers.GetOrAdd(eventKey, _ =>
{
var container = new EventContainer();
BridgeConnector.Socket.On(eventKey, container.OnEventAction);
BridgeConnector.Socket.Emit($"register-{eventName}", id);
return container;
});

container.Register(value);
}
}

protected void RemoveEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = EventName(callerName);
var eventKey = EventKey(eventName, id);

lock (objLock)
{
if (eventContainers.TryGetValue(eventKey, out var container) && !container.Unregister(value))
{
BridgeConnector.Socket.Off(eventKey);
eventContainers.TryRemove(eventKey, out _);
}
}
}

protected void AddEvent<T>(Action<T> value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");

var eventName = EventName(callerName);
var eventKey = EventKey(eventName, id);

lock (objLock)
{
var container = eventContainers.GetOrAdd(eventKey, _ =>
{
var container = new EventContainer();
BridgeConnector.Socket.On<T>(eventKey, container.OnEventActionT);
BridgeConnector.Socket.Emit($"register-{eventName}", id);
return container;
});

container.Register(value);
}
}

protected void RemoveEvent<T>(Action<T> value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = EventName(callerName);
var eventKey = EventKey(eventName, id);

lock (objLock)
{
if (eventContainers.TryGetValue(eventKey, out var container) && !container.Unregister(value))
{
BridgeConnector.Socket.Off(eventKey);
eventContainers.TryRemove(eventKey, out _);
}
}
}

private string EventName(string callerName)
{
switch (SocketEventNameType)
{
case SocketEventNameTypes.DashedLower:
return $"{objectName}-{callerName.ToDashedEventName()}";
case SocketEventNameTypes.CamelCase:
return $"{objectName}-{callerName.ToCamelCaseEventName()}";
default:
throw new ArgumentOutOfRangeException();
}
}

private string EventKey(string eventName, int? id)
{
return string.Format(CultureInfo.InvariantCulture, "{0}{1:D}", eventName, id);
}

internal abstract class PropertyGetter
{
Expand All @@ -131,26 +238,37 @@ internal class PropertyGetter<T> : PropertyGetter
private readonly Task<T> tcsTask;
private TaskCompletionSource<T> tcs;

public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs, object arg = null)
{
this.tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
this.tcsTask = this.tcs.Task;

string eventName;
string messageName;

switch (apiBase.SocketEventNameType)
switch (apiBase.SocketTaskEventNameType)
{
case SocketEventNameTypes.DashesLowerFirst:
case SocketTaskEventNameTypes.DashesLowerFirst:
eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}-completed");
break;
case SocketEventNameTypes.NoDashUpperFirst:
case SocketTaskEventNameTypes.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());

switch (apiBase.SocketTaskMessageNameType)
{
case SocketTaskMessageNameTypes.DashesLowerFirst:
messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}");
break;
case SocketTaskMessageNameTypes.NoDashUpperFirst:
messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
break;
default:
throw new ArgumentOutOfRangeException();
}

BridgeConnector.Socket.Once<T>(eventName, (result) =>
{
Expand All @@ -171,14 +289,14 @@ public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
}
}
});

if (apiBase.Id >= 0)
if (arg != null)
Comment thread
FlorianRappl marked this conversation as resolved.
{
BridgeConnector.Socket.Emit(messageName, apiBase.Id);
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id, arg) : BridgeConnector.Socket.Emit(messageName, arg);
}
else
{
BridgeConnector.Socket.Emit(messageName);
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id) : BridgeConnector.Socket.Emit(messageName);
}

System.Threading.Tasks.Task.Delay(PropertyTimeout).ContinueWith(_ =>
Expand All @@ -203,5 +321,53 @@ public override Task<T1> Task<T1>()
return this.tcsTask as Task<T1>;
}
}

[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")]
private class EventContainer
{
private Action eventAction;
private Delegate eventActionT;

private Action<T> GetEventActionT<T>()
{
return (Action<T>)eventActionT;
}

private void SetEventActionT<T>(Action<T> actionT)
{
eventActionT = actionT;
}

public void OnEventAction() => eventAction?.Invoke();

public void OnEventActionT<T>(T p) => GetEventActionT<T>()?.Invoke(p);

public void Register(Action receiver)
{
eventAction += receiver;
}

public void Register<T>(Action<T> receiver)
{
var actionT = GetEventActionT<T>();
actionT += receiver;
SetEventActionT(actionT);
}

public bool Unregister(Action receiver)
{
eventAction -= receiver;
return this.eventAction != null;
}

public bool Unregister<T>(Action<T> receiver)
{
var actionT = GetEventActionT<T>();
actionT -= receiver;
SetEventActionT(actionT);

return actionT != null;
}
}
}
}
Loading
Loading