Skip to content

Commit

Permalink
Remove TAPI address notification properly, if all connections of a us…
Browse files Browse the repository at this point in the history
…er have disconnected
  • Loading branch information
C0nquistadore committed Sep 19, 2022
1 parent 2a02964 commit 9c57d6d
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 27 deletions.
14 changes: 14 additions & 0 deletions src/PhoneBox.Abstractions/CallSubscriberConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace PhoneBox.Abstractions
{
public readonly struct CallSubscriberConnection
{
public string ConnectionId { get; }
public CallSubscriber Subscriber { get; }

public CallSubscriberConnection(string connectionId, CallSubscriber subscriber)
{
this.ConnectionId = connectionId;
this.Subscriber = subscriber;
}
}
}
3 changes: 2 additions & 1 deletion src/PhoneBox.Abstractions/ITelephonyConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
public interface ITelephonyConnector
{
void Subscribe(CallSubscriber subscriber);
void Subscribe(CallSubscriberConnection connection);
void Unsubscribe(CallSubscriberConnection connection);
}
}
38 changes: 33 additions & 5 deletions src/PhoneBox.Server/SignalR/TelephonyHub.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading.Tasks;
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using PhoneBox.Abstractions;
Expand All @@ -13,15 +15,41 @@ public partial class TelephonyHub : Hub<ITelephonyHub>
public TelephonyHub(ITelephonyConnector connector, ILogger<TelephonyHub> logger)
{
_connector = connector;
this._logger = logger;
_logger = logger;
}

public override Task OnConnectedAsync()
{
string userid = Context.UserIdentifier!;
_connector.Subscribe(new CallSubscriber(userid));
_logger.LogInformation("Client connected: {Userid}", userid);
string connectionId = Context.ConnectionId;
string userid = GetUserIdSafe();
_connector.Subscribe(new CallSubscriberConnection(connectionId, new CallSubscriber(userid)));
_logger.LogInformation("Client connected: [ConnectionId: {ConnectionId}] [UserId: {Userid}]", connectionId, userid);
return Task.CompletedTask;
}

public override Task OnDisconnectedAsync(Exception? exception)
{
string connectionId = Context.ConnectionId;
string userid = GetUserIdSafe();
_connector.Unsubscribe(new CallSubscriberConnection(connectionId, new CallSubscriber(userid)));
StringBuilder sb = new StringBuilder("Client disconnected: [ConnectionId: {ConnectionId}] [UserId: {Userid}]");
if (exception != null)
{
sb.Append(@"
Exception: {Exception}");
}
string message = sb.ToString();
_logger.LogInformation(message, connectionId, userid, exception);
return Task.CompletedTask;
}

private string GetUserIdSafe()
{
string? userIdentifier = Context.UserIdentifier;
if (String.IsNullOrEmpty(userIdentifier))
throw new InvalidOperationException("Unresolved user identifier from SignalR connection");

return userIdentifier;
}
}
}
3 changes: 2 additions & 1 deletion src/PhoneBox.Server/WebHook/WebHookConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace PhoneBox.Server.WebHook
{
internal sealed class WebHookConnector : ITelephonyConnector
{
void ITelephonyConnector.Subscribe(CallSubscriber subscriber) { }
void ITelephonyConnector.Subscribe(CallSubscriberConnection connection) { }
void ITelephonyConnector.Unsubscribe(CallSubscriberConnection connection) { }
}
}
104 changes: 84 additions & 20 deletions src/PhoneBox.TapiService/TapiConnector.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
Expand All @@ -13,22 +14,22 @@ public sealed class TapiConnector : IHostedService, ITelephonyConnector
{
private readonly ITelephonyEventDispatcherFactory _eventDispatcherFactory;
private readonly ILogger<TapiConnector> _logger;
private readonly TapiEventNotificationSink _callNotification;
private readonly TapiEventNotificationSink _callNotificationSink;

private TAPIClass? _tapiClient;

public TapiConnector(ITelephonyEventDispatcherFactory eventDispatcherFactory, ILogger<TapiConnector> logger)
{
_eventDispatcherFactory = eventDispatcherFactory;
_logger = logger;
_callNotification = new TapiEventNotificationSink();
_callNotificationSink = new TapiEventNotificationSink();
}

Task IHostedService.StartAsync(CancellationToken cancellationToken)
{
_tapiClient = new TAPIClass();
_tapiClient.Initialize();
_tapiClient.ITTAPIEventNotification_Event_Event += _callNotification.Event;
_tapiClient.ITTAPIEventNotification_Event_Event += _callNotificationSink.Event;
_tapiClient.EventFilter = (int)(
TAPI_EVENT.TE_CALLNOTIFICATION |
TAPI_EVENT.TE_CALLSTATE |
Expand All @@ -43,31 +44,41 @@ Task IHostedService.StartAsync(CancellationToken cancellationToken)

Task IHostedService.StopAsync(CancellationToken cancellationToken)
{
_tapiClient!.ITTAPIEventNotification_Event_Event -= _callNotification!.Event;
_tapiClient!.Shutdown();
TAPIClass client = GetClientSafe();
client.ITTAPIEventNotification_Event_Event -= _callNotificationSink!.Event;
client.Shutdown();
return Task.CompletedTask;
}

void ITelephonyConnector.Subscribe(CallSubscriber subscriber)
void ITelephonyConnector.Subscribe(CallSubscriberConnection connection)
{
CallSubscriber subscriber = connection.Subscriber;
ITAddress? tapiAddress = FindTapiAddressForSubscriber(subscriber);
if (tapiAddress == null)
{
// Boom?
throw new InvalidOperationException($"Could not resolve TAPI address by phone number: {subscriber.PhoneNumber}");
}

if (_callNotificationSink.IsRegistered(tapiAddress))
{
_callNotificationSink.AddConnectionToSubscription(connection);
}
else
{
_tapiClient!.RegisterCallNotifications(tapiAddress, fMonitor: true, fOwner: true, lMediaTypes: TapiConstants.TAPIMEDIATYPE_AUDIO, lCallbackInstance: 0);

int subscriptionId = GetClientSafe().RegisterCallNotifications(tapiAddress, fMonitor: true, fOwner: true, lMediaTypes: TapiConstants.TAPIMEDIATYPE_AUDIO, lCallbackInstance: 0);
ITelephonyEventDispatcher eventDispatcher = _eventDispatcherFactory.Create(subscriber);
_callNotification.AddAddressRegistration(tapiAddress, subscriber, eventDispatcher);
_callNotificationSink.AddSubscriber(tapiAddress, connection, eventDispatcher, subscriptionId);
}
}

void ITelephonyConnector.Unsubscribe(CallSubscriberConnection connection)
{
_callNotificationSink.RemoveSubscriber(connection, GetClientSafe());
}

private ITAddress? FindTapiAddressForSubscriber(CallSubscriber subscriber)
{
IEnumAddress addresses = _tapiClient!.EnumerateAddresses();
IEnumAddress addresses = GetClientSafe().EnumerateAddresses();
uint fetched = 0;
do
{
Expand All @@ -91,19 +102,62 @@ void ITelephonyConnector.Subscribe(CallSubscriber subscriber)
return null;
}

private TAPIClass GetClientSafe()
{
if (_tapiClient == null)
throw new InvalidOperationException("Tapi client not initialized");

return _tapiClient;
}

private sealed class TapiEventNotificationSink : ITTAPIEventNotification
{
private readonly IDictionary<string, TapiAddressSubscription> _registrations;
private readonly IDictionary<string, TapiAddressSubscription> _addressSubscriptionMap;
private readonly IDictionary<string, TapiAddressSubscription> _subscriberAddressMap;

public TapiEventNotificationSink()
{
_registrations = new SortedDictionary<string, TapiAddressSubscription>();
_addressSubscriptionMap = new SortedDictionary<string, TapiAddressSubscription>();
_subscriberAddressMap = new Dictionary<string, TapiAddressSubscription>();
}

public void AddAddressRegistration(ITAddress address, CallSubscriber subscriber, ITelephonyEventDispatcher eventDispatcher)
public bool IsRegistered(ITAddress tapiAddress) => _addressSubscriptionMap.ContainsKey(tapiAddress.AddressName);

public void AddConnectionToSubscription(CallSubscriberConnection connection)
{
var addressRegistration = new TapiAddressSubscription(address, subscriber, eventDispatcher);
_registrations.Add(address.AddressName, addressRegistration);
TapiAddressSubscription subscription = GetSubscription(connection.Subscriber);
subscription.Connections.Add(connection);
}

public void AddSubscriber(ITAddress address, CallSubscriberConnection connection, ITelephonyEventDispatcher eventDispatcher, int subscriptionId)
{
string addressName = address.AddressName;
if (_addressSubscriptionMap.ContainsKey(addressName))
{
throw new InvalidOperationException(@$"Address already registered: {addressName}
ConnectionId: {connection.ConnectionId}
SubscriberPhoneNumber: {connection.Subscriber.PhoneNumber}");
}

var addressRegistration = new TapiAddressSubscription(address, eventDispatcher, subscriptionId, connection);
_addressSubscriptionMap.Add(addressName, addressRegistration);
_subscriberAddressMap.Add(connection.Subscriber.PhoneNumber, addressRegistration);
}

public void RemoveSubscriber(CallSubscriberConnection connection, ITTAPI client)
{
CallSubscriber subscriber = connection.Subscriber;
TapiAddressSubscription subscription = GetSubscription(subscriber);
if (subscription.Connections.Count < 2)
{
client.UnregisterNotifications(subscription.SubscriptionId);
_subscriberAddressMap.Remove(subscriber.PhoneNumber);
_addressSubscriptionMap.Remove(subscription.AddressName);
}
else
{
subscription.Connections.Remove(connection);
}
}

public async void Event(TAPI_EVENT tapiEvent, object pEvent)
Expand All @@ -119,7 +173,7 @@ public async void Event(TAPI_EVENT tapiEvent, object pEvent)
private async Task PublishCallStateEvent(ITCallStateEvent stateEvent)
{
ITCallInfo call = stateEvent.Call;
if (!_registrations.TryGetValue(call.Address.AddressName, out TapiAddressSubscription registration))
if (!_addressSubscriptionMap.TryGetValue(call.Address.AddressName, out TapiAddressSubscription registration))
return;

string phoneNumber = call.CallInfoString[CALLINFO_STRING.CIS_CALLERIDNUMBER];
Expand All @@ -134,19 +188,29 @@ private async Task PublishCallStateEvent(ITCallStateEvent stateEvent)
break;
}
}

private TapiAddressSubscription GetSubscription(CallSubscriber subscriber)
{
if (!_subscriberAddressMap.TryGetValue(subscriber.PhoneNumber, out TapiAddressSubscription subscription))
throw new InvalidOperationException($"No subscriptions registered for phone number: {subscriber.PhoneNumber}");

return subscription;
}
}

private readonly struct TapiAddressSubscription
{
public string AddressName { get; }
public CallSubscriber Subscriber { get; }
public ITelephonyEventDispatcher Publisher { get; }
public int SubscriptionId { get; }
public ICollection<CallSubscriberConnection> Connections { get; }

public TapiAddressSubscription(ITAddress address, CallSubscriber subscriber, ITelephonyEventDispatcher publisher)
public TapiAddressSubscription(ITAddress address, ITelephonyEventDispatcher publisher, int subscriptionId, CallSubscriberConnection connection)
{
AddressName = address.AddressName;
Subscriber = subscriber;
Publisher = publisher;
SubscriptionId = subscriptionId;
Connections = new Collection<CallSubscriberConnection> { connection };
}
}
}
Expand Down

0 comments on commit 9c57d6d

Please sign in to comment.