mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-10-27 00:17:31 +00:00
275 lines
9.5 KiB
C#
275 lines
9.5 KiB
C#
using CryptoExchange.Net.Sockets;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CryptoExchange.Net.Objects.Sockets
|
|
{
|
|
/// <summary>
|
|
/// Subscription to a data stream
|
|
/// </summary>
|
|
public class UpdateSubscription
|
|
{
|
|
private readonly SocketConnection _connection;
|
|
private readonly Subscription _listener;
|
|
|
|
private object _eventLock = new object();
|
|
private bool _connectionEventsSubscribed = true;
|
|
private List<Action> _connectionClosedEventHandlers = new List<Action>();
|
|
private List<Action> _connectionLostEventHandlers = new List<Action>();
|
|
private List<Action<Error>> _resubscribeFailedEventHandlers = new List<Action<Error>>();
|
|
private List<Action<TimeSpan>> _connectionRestoredEventHandlers = new List<Action<TimeSpan>>();
|
|
private List<Action> _activityPausedEventHandlers = new List<Action>();
|
|
private List<Action> _activityUnpausedEventHandlers = new List<Action>();
|
|
|
|
/// <summary>
|
|
/// Event when the status of the subscription changes
|
|
/// </summary>
|
|
public event Action<SubscriptionStatus>? SubscriptionStatusChanged;
|
|
|
|
/// <summary>
|
|
/// Event when the connection is lost. The socket will automatically reconnect when possible.
|
|
/// </summary>
|
|
public event Action ConnectionLost
|
|
{
|
|
add { lock (_eventLock) _connectionLostEventHandlers.Add(value); }
|
|
remove { lock (_eventLock) _connectionLostEventHandlers.Remove(value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event when the connection is closed and will not be reconnected
|
|
/// </summary>
|
|
public event Action ConnectionClosed
|
|
{
|
|
add { lock (_eventLock) _connectionClosedEventHandlers.Add(value); }
|
|
remove { lock (_eventLock) _connectionClosedEventHandlers.Remove(value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event when a lost connection is restored, but the resubscribing of update subscriptions failed
|
|
/// </summary>
|
|
public event Action<Error> ResubscribingFailed
|
|
{
|
|
add { lock (_eventLock) _resubscribeFailedEventHandlers.Add(value); }
|
|
remove { lock (_eventLock) _resubscribeFailedEventHandlers.Remove(value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public event Action<TimeSpan> ConnectionRestored
|
|
{
|
|
add { lock (_eventLock) _connectionRestoredEventHandlers.Add(value); }
|
|
remove { lock (_eventLock) _connectionRestoredEventHandlers.Remove(value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event when the connection to the server is paused based on a server indication. No operations can be performed while paused
|
|
/// </summary>
|
|
public event Action ActivityPaused
|
|
{
|
|
add { lock (_eventLock) _activityPausedEventHandlers.Add(value); }
|
|
remove { lock (_eventLock) _activityPausedEventHandlers.Remove(value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event when the connection to the server is unpaused after being paused
|
|
/// </summary>
|
|
public event Action ActivityUnpaused
|
|
{
|
|
add { lock (_eventLock) _activityUnpausedEventHandlers.Add(value); }
|
|
remove { lock (_eventLock) _activityUnpausedEventHandlers.Remove(value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event when an exception happens during the handling of the data
|
|
/// </summary>
|
|
public event Action<Exception> Exception
|
|
{
|
|
add => _listener.Exception += value;
|
|
remove => _listener.Exception -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The id of the socket
|
|
/// </summary>
|
|
public int SocketId => _connection.SocketId;
|
|
|
|
/// <summary>
|
|
/// The id of the subscription
|
|
/// </summary>
|
|
public int Id => _listener.Id;
|
|
|
|
/// <summary>
|
|
/// ctor
|
|
/// </summary>
|
|
/// <param name="connection">The socket connection the subscription is on</param>
|
|
/// <param name="subscription">The subscription</param>
|
|
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;
|
|
|
|
_listener = subscription;
|
|
_listener.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 (!_listener.IsClosingConnection)
|
|
return;
|
|
|
|
List<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _connectionClosedEventHandlers.ToList();
|
|
|
|
foreach(var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
private void HandleConnectionLostEvent()
|
|
{
|
|
if (!_listener.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _connectionLostEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
private void HandleConnectionRestoredEvent(TimeSpan period)
|
|
{
|
|
if (!_listener.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action<TimeSpan>> handlers;
|
|
lock (_eventLock)
|
|
handlers = _connectionRestoredEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback(period);
|
|
}
|
|
|
|
private void HandleResubscribeFailedEvent(Error error)
|
|
{
|
|
if (!_listener.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action<Error>> handlers;
|
|
lock (_eventLock)
|
|
handlers = _resubscribeFailedEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback(error);
|
|
}
|
|
|
|
private void HandlePausedEvent()
|
|
{
|
|
if (!_listener.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _activityPausedEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
private void HandleUnpausedEvent()
|
|
{
|
|
if (!_listener.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _activityUnpausedEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the subscription
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Task CloseAsync()
|
|
{
|
|
return _connection.CloseAsync(_listener);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the socket to cause a reconnect
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Task ReconnectAsync()
|
|
{
|
|
return _connection.TriggerReconnectAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribe a subscription
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal async Task UnsubscribeAsync()
|
|
{
|
|
await _connection.UnsubscribeAsync(_listener).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resubscribe this subscription
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal async Task<CallResult> ResubscribeAsync()
|
|
{
|
|
return await _connection.ResubscribeAsync(_listener).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|