mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2026-02-16 14:13:46 +00:00
Added user data tracking logic Added LastReceiveTime, SocketStatus and SubscriptionStatus properties to UpdateSubscription Added SharedTransferStatus Enum and property to SharedDeposit Added PositionMode property to SharedPosition model Added IsZero property to SharedQuantity Renamed IWebSocket LastActionTime to LastReceiveTime Updated CryptoExchangeWebsocketClient LastReceiveTime logic Updated Subscription status change event handler to run sync instead of separate task
296 lines
10 KiB
C#
296 lines
10 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Subscription to a data stream
|
|
/// </summary>
|
|
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<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 => _subscription.Exception += value;
|
|
remove => _subscription.Exception -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The id of the socket
|
|
/// </summary>
|
|
public int SocketId => _connection.SocketId;
|
|
|
|
/// <summary>
|
|
/// The id of the subscription
|
|
/// </summary>
|
|
public int Id => _subscription.Id;
|
|
|
|
/// <summary>
|
|
/// The last timestamp anything was received from the server
|
|
/// </summary>
|
|
public DateTime? LastReceiveTime => _connection.LastReceiveTime;
|
|
|
|
/// <summary>
|
|
/// The current websocket status
|
|
/// </summary>
|
|
public SocketStatus SocketStatus => _connection.Status;
|
|
|
|
/// <summary>
|
|
/// The current subscription status
|
|
/// </summary>
|
|
public SubscriptionStatus SubscriptionStatus => _subscription.Status;
|
|
|
|
/// <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;
|
|
|
|
_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<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _connectionClosedEventHandlers.ToList();
|
|
|
|
foreach(var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
private void HandleConnectionLostEvent()
|
|
{
|
|
if (!_subscription.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _connectionLostEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
private void HandleConnectionRestoredEvent(TimeSpan period)
|
|
{
|
|
if (!_subscription.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 (!_subscription.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action<Error>> handlers;
|
|
lock (_eventLock)
|
|
handlers = _resubscribeFailedEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback(error);
|
|
}
|
|
|
|
private void HandlePausedEvent()
|
|
{
|
|
if (!_subscription.Active)
|
|
{
|
|
UnsubscribeConnectionEvents();
|
|
return;
|
|
}
|
|
|
|
List<Action> handlers;
|
|
lock (_eventLock)
|
|
handlers = _activityPausedEventHandlers.ToList();
|
|
|
|
foreach (var callback in handlers)
|
|
callback();
|
|
}
|
|
|
|
private void HandleUnpausedEvent()
|
|
{
|
|
if (!_subscription.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(_subscription);
|
|
}
|
|
|
|
/// <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(_subscription).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resubscribe this subscription
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal async Task<CallResult> ResubscribeAsync()
|
|
{
|
|
return await _connection.ResubscribeAsync(_subscription).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|