using CryptoExchange.Net.Sockets.Default;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects.Sockets
{
///
/// Subscription to a data stream
///
public class UpdateSubscription
{
private readonly SocketConnection _connection;
internal readonly Subscription _subscription;
#if NET9_0_OR_GREATER
private readonly Lock _eventLock = new Lock();
#else
private readonly object _eventLock = new object();
#endif
private bool _connectionEventsSubscribed = true;
private List _connectionClosedEventHandlers = new List();
private List _connectionLostEventHandlers = new List();
private List> _resubscribeFailedEventHandlers = new List>();
private List> _connectionRestoredEventHandlers = new List>();
private List _activityPausedEventHandlers = new List();
private List _activityUnpausedEventHandlers = new List();
///
/// Event when the status of the subscription changes
///
public event Action? SubscriptionStatusChanged;
///
/// Event when the connection is lost. The socket will automatically reconnect when possible.
///
public event Action ConnectionLost
{
add { lock (_eventLock) _connectionLostEventHandlers.Add(value); }
remove { lock (_eventLock) _connectionLostEventHandlers.Remove(value); }
}
///
/// Event when the connection is closed and will not be reconnected
///
public event Action ConnectionClosed
{
add { lock (_eventLock) _connectionClosedEventHandlers.Add(value); }
remove { lock (_eventLock) _connectionClosedEventHandlers.Remove(value); }
}
///
/// Event when a lost connection is restored, but the resubscribing of update subscriptions failed
///
public event Action ResubscribingFailed
{
add { lock (_eventLock) _resubscribeFailedEventHandlers.Add(value); }
remove { lock (_eventLock) _resubscribeFailedEventHandlers.Remove(value); }
}
///
/// Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting.
/// Note that when the executing code is suspended and resumed at a later period (for example, a laptop going to sleep) the disconnect time will be incorrect as the disconnect
/// will only be detected after resuming the code, so the initial disconnect time is lost. Use the timespan only for informational purposes.
///
public event Action ConnectionRestored
{
add { lock (_eventLock) _connectionRestoredEventHandlers.Add(value); }
remove { lock (_eventLock) _connectionRestoredEventHandlers.Remove(value); }
}
///
/// Event when the connection to the server is paused based on a server indication. No operations can be performed while paused
///
public event Action ActivityPaused
{
add { lock (_eventLock) _activityPausedEventHandlers.Add(value); }
remove { lock (_eventLock) _activityPausedEventHandlers.Remove(value); }
}
///
/// Event when the connection to the server is unpaused after being paused
///
public event Action ActivityUnpaused
{
add { lock (_eventLock) _activityUnpausedEventHandlers.Add(value); }
remove { lock (_eventLock) _activityUnpausedEventHandlers.Remove(value); }
}
///
/// Event when an exception happens during the handling of the data
///
public event Action Exception
{
add => _subscription.Exception += value;
remove => _subscription.Exception -= value;
}
///
/// The id of the socket
///
public int SocketId => _connection.SocketId;
///
/// The id of the subscription
///
public int Id => _subscription.Id;
///
/// The last timestamp anything was received from the server
///
public DateTime? LastReceiveTime => _connection.LastReceiveTime;
///
/// The current websocket status
///
public SocketStatus SocketStatus => _connection.Status;
///
/// The current subscription status
///
public SubscriptionStatus SubscriptionStatus => _subscription.Status;
///
/// ctor
///
/// The socket connection the subscription is on
/// The subscription
public UpdateSubscription(SocketConnection connection, Subscription subscription)
{
_connection = connection;
_connection.ConnectionClosed += HandleConnectionClosedEvent;
_connection.ConnectionLost += HandleConnectionLostEvent;
_connection.ConnectionRestored += HandleConnectionRestoredEvent;
_connection.ResubscribingFailed += HandleResubscribeFailedEvent;
_connection.ActivityPaused += HandlePausedEvent;
_connection.ActivityUnpaused += HandleUnpausedEvent;
_subscription = subscription;
_subscription.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x);
}
private void UnsubscribeConnectionEvents()
{
lock (_eventLock)
{
if (!_connectionEventsSubscribed)
return;
_connection.ConnectionClosed -= HandleConnectionClosedEvent;
_connection.ConnectionLost -= HandleConnectionLostEvent;
_connection.ConnectionRestored -= HandleConnectionRestoredEvent;
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
_connection.ActivityPaused -= HandlePausedEvent;
_connection.ActivityUnpaused -= HandleUnpausedEvent;
_connectionEventsSubscribed = false;
}
}
private void HandleConnectionClosedEvent()
{
UnsubscribeConnectionEvents();
// If we're not the subscription closing this connection don't bother emitting
if (!_subscription.IsClosingConnection)
return;
List handlers;
lock (_eventLock)
handlers = _connectionClosedEventHandlers.ToList();
foreach(var callback in handlers)
callback();
}
private void HandleConnectionLostEvent()
{
if (!_subscription.Active)
{
UnsubscribeConnectionEvents();
return;
}
List handlers;
lock (_eventLock)
handlers = _connectionLostEventHandlers.ToList();
foreach (var callback in handlers)
callback();
}
private void HandleConnectionRestoredEvent(TimeSpan period)
{
if (!_subscription.Active)
{
UnsubscribeConnectionEvents();
return;
}
List> handlers;
lock (_eventLock)
handlers = _connectionRestoredEventHandlers.ToList();
foreach (var callback in handlers)
callback(period);
}
private void HandleResubscribeFailedEvent(Error error)
{
if (!_subscription.Active)
{
UnsubscribeConnectionEvents();
return;
}
List> handlers;
lock (_eventLock)
handlers = _resubscribeFailedEventHandlers.ToList();
foreach (var callback in handlers)
callback(error);
}
private void HandlePausedEvent()
{
if (!_subscription.Active)
{
UnsubscribeConnectionEvents();
return;
}
List handlers;
lock (_eventLock)
handlers = _activityPausedEventHandlers.ToList();
foreach (var callback in handlers)
callback();
}
private void HandleUnpausedEvent()
{
if (!_subscription.Active)
{
UnsubscribeConnectionEvents();
return;
}
List handlers;
lock (_eventLock)
handlers = _activityUnpausedEventHandlers.ToList();
foreach (var callback in handlers)
callback();
}
///
/// Close the subscription
///
///
public Task CloseAsync()
{
return _connection.CloseAsync(_subscription);
}
///
/// Close the socket to cause a reconnect
///
///
public Task ReconnectAsync()
{
return _connection.TriggerReconnectAsync();
}
///
/// Unsubscribe a subscription
///
///
internal async Task UnsubscribeAsync()
{
await _connection.UnsubscribeAsync(_subscription).ConfigureAwait(false);
}
///
/// Resubscribe this subscription
///
///
internal async Task ResubscribeAsync()
{
return await _connection.ResubscribeAsync(_subscription).ConfigureAwait(false);
}
}
}