| @@ -22,6 +22,12 @@ namespace Discord | |||
| VoiceWebSocket, | |||
| } | |||
| public class DisconnectedEventArgs : EventArgs | |||
| { | |||
| public readonly bool WasUnexpected; | |||
| public readonly Exception Error; | |||
| internal DisconnectedEventArgs(bool wasUnexpected, Exception error) { WasUnexpected = wasUnexpected; Error = error; } | |||
| } | |||
| public sealed class LogMessageEventArgs : EventArgs | |||
| { | |||
| public LogMessageSeverity Severity { get; } | |||
| @@ -30,6 +36,7 @@ namespace Discord | |||
| internal LogMessageEventArgs(LogMessageSeverity severity, LogMessageSource source, string msg) { Severity = severity; Source = source; Message = msg; } | |||
| } | |||
| public sealed class ServerEventArgs : EventArgs | |||
| { | |||
| public Server Server { get; } | |||
| @@ -136,11 +143,11 @@ namespace Discord | |||
| if (Connected != null) | |||
| Connected(this, EventArgs.Empty); | |||
| } | |||
| public event EventHandler Disconnected; | |||
| private void RaiseDisconnected() | |||
| public event EventHandler<DisconnectedEventArgs> Disconnected; | |||
| private void RaiseDisconnected(DisconnectedEventArgs e) | |||
| { | |||
| if (Disconnected != null) | |||
| Disconnected(this, EventArgs.Empty); | |||
| Disconnected(this, e); | |||
| } | |||
| public event EventHandler<LogMessageEventArgs> LogMessage; | |||
| internal void RaiseOnLog(LogMessageSeverity severity, LogMessageSource source, string message) | |||
| @@ -308,11 +315,11 @@ namespace Discord | |||
| if (VoiceConnected != null) | |||
| VoiceConnected(this, EventArgs.Empty); | |||
| } | |||
| public event EventHandler VoiceDisconnected; | |||
| private void RaiseVoiceDisconnected() | |||
| public event EventHandler<DisconnectedEventArgs> VoiceDisconnected; | |||
| private void RaiseVoiceDisconnected(DisconnectedEventArgs e) | |||
| { | |||
| if (VoiceDisconnected != null) | |||
| VoiceDisconnected(this, EventArgs.Empty); | |||
| VoiceDisconnected(this, e); | |||
| } | |||
| /*public event EventHandler<VoiceServerUpdatedEventArgs> VoiceServerChanged; | |||
| private void RaiseVoiceServerUpdated(Server server, string endpoint) | |||
| @@ -38,7 +38,10 @@ namespace Discord | |||
| public void SendVoicePCM(byte[] data, int count) | |||
| { | |||
| CheckReady(checkVoice: true); | |||
| if (data == null) throw new ArgumentException(nameof(data)); | |||
| if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | |||
| if (count == 0) return; | |||
| _voiceSocket.SendPCMFrames(data, count); | |||
| } | |||
| @@ -46,6 +49,7 @@ namespace Discord | |||
| public void ClearVoicePCM() | |||
| { | |||
| CheckReady(checkVoice: true); | |||
| _voiceSocket.ClearPCMFrames(); | |||
| } | |||
| @@ -53,6 +57,7 @@ namespace Discord | |||
| public async Task WaitVoice() | |||
| { | |||
| CheckReady(checkVoice: true); | |||
| _voiceSocket.Wait(); | |||
| await TaskHelper.CompletedTask.ConfigureAwait(false); | |||
| } | |||
| @@ -35,6 +35,7 @@ namespace Discord | |||
| private Task _runTask; | |||
| protected ExceptionDispatchInfo _disconnectReason; | |||
| private bool _wasDisconnectUnexpected; | |||
| private string _token; | |||
| /// <summary> Returns the id of the current logged-in user. </summary> | |||
| public string CurrentUserId => _currentUserId; | |||
| @@ -86,6 +87,7 @@ namespace Discord | |||
| _config.Lock(); | |||
| _state = (int)DiscordClientState.Disconnected; | |||
| _cancelToken = new CancellationToken(true); | |||
| _disconnectedEvent = new ManualResetEvent(true); | |||
| _connectedEvent = new ManualResetEventSlim(false); | |||
| _rand = new Random(); | |||
| @@ -93,8 +95,13 @@ namespace Discord | |||
| _api = new DiscordAPIClient(_config.LogLevel); | |||
| _dataSocket = new DataWebSocket(this); | |||
| _dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; | |||
| _dataSocket.Disconnected += async (s, e) => { RaiseDisconnected(e); if (e.WasUnexpected) await Connect(_token); /*await _dataSocket.Reconnect(_cancelToken);*/ }; | |||
| if (_config.EnableVoice) | |||
| { | |||
| _voiceSocket = new VoiceWebSocket(this); | |||
| _voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); | |||
| _voiceSocket.Disconnected += async (s, e) => { RaiseVoiceDisconnected(e); if (e.WasUnexpected) await _voiceSocket.Reconnect(_cancelToken); }; | |||
| } | |||
| _channels = new Channels(this); | |||
| _members = new Members(this); | |||
| @@ -108,10 +115,6 @@ namespace Discord | |||
| _voiceSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.VoiceWebSocket, e.Message); | |||
| if (_config.LogLevel >= LogMessageSeverity.Info) | |||
| { | |||
| Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, "Connected"); | |||
| Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, "Disconnected"); | |||
| VoiceConnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Voice Connected"); | |||
| VoiceDisconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Voice Disconnected"); | |||
| _dataSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Connected"); | |||
| _dataSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Disconnected"); | |||
| //_dataSocket.ReceivedEvent += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, $"Received {e.Type}"); | |||
| @@ -604,6 +607,7 @@ namespace Discord | |||
| } | |||
| //_state = (int)DiscordClientState.Connected; | |||
| _token = token; | |||
| return token; | |||
| } | |||
| catch | |||
| @@ -616,23 +620,24 @@ namespace Discord | |||
| { | |||
| _state = (int)WebSocketState.Connected; | |||
| _connectedEvent.Set(); | |||
| RaiseConnected(); | |||
| } | |||
| /// <summary> Disconnects from the Discord server, canceling any pending requests. </summary> | |||
| public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false); | |||
| protected async Task DisconnectInternal(Exception ex, bool isUnexpected = true, bool skipAwait = false) | |||
| protected Task DisconnectInternal(Exception ex, bool isUnexpected = true, bool skipAwait = false) | |||
| { | |||
| int oldState; | |||
| bool hasWriterLock; | |||
| //If in either connecting or connected state, get a lock by being the first to switch to disconnecting | |||
| oldState = Interlocked.CompareExchange(ref _state, (int)DiscordClientState.Disconnecting, (int)DiscordClientState.Connecting); | |||
| if (oldState == (int)DiscordClientState.Disconnected) return; //Already disconnected | |||
| if (oldState == (int)DiscordClientState.Disconnected) return TaskHelper.CompletedTask; //Already disconnected | |||
| hasWriterLock = oldState == (int)DiscordClientState.Connecting; //Caused state change | |||
| if (!hasWriterLock) | |||
| { | |||
| oldState = Interlocked.CompareExchange(ref _state, (int)DiscordClientState.Disconnecting, (int)DiscordClientState.Connected); | |||
| if (oldState == (int)DiscordClientState.Disconnected) return; //Already disconnected | |||
| if (oldState == (int)DiscordClientState.Disconnected) return TaskHelper.CompletedTask; //Already disconnected | |||
| hasWriterLock = oldState == (int)DiscordClientState.Connected; //Caused state change | |||
| } | |||
| @@ -644,18 +649,9 @@ namespace Discord | |||
| } | |||
| if (!skipAwait) | |||
| { | |||
| Task task = _runTask; | |||
| if (task != null) | |||
| await task.ConfigureAwait(false); | |||
| } | |||
| if (hasWriterLock) | |||
| { | |||
| _state = (int)DiscordClientState.Disconnected; | |||
| _disconnectedEvent.Set(); | |||
| _connectedEvent.Reset(); | |||
| } | |||
| return _runTask ?? TaskHelper.CompletedTask; | |||
| else | |||
| return TaskHelper.CompletedTask; | |||
| } | |||
| private async Task RunTasks() | |||
| @@ -672,13 +668,14 @@ namespace Discord | |||
| } | |||
| catch (Exception ex) { await DisconnectInternal(ex, skipAwait: true).ConfigureAwait(false); } | |||
| await Cleanup().ConfigureAwait(false); | |||
| bool wasUnexpected = _wasDisconnectUnexpected; | |||
| _wasDisconnectUnexpected = false; | |||
| await Cleanup(wasUnexpected).ConfigureAwait(false); | |||
| _runTask = null; | |||
| } | |||
| private async Task Cleanup() | |||
| private async Task Cleanup(bool wasUnexpected) | |||
| { | |||
| _disconnectedEvent.Set(); | |||
| await _dataSocket.Disconnect().ConfigureAwait(false); | |||
| if (_config.EnableVoice) | |||
| await _voiceSocket.Disconnect().ConfigureAwait(false); | |||
| @@ -695,6 +692,14 @@ namespace Discord | |||
| _currentUser = null; | |||
| _currentUserId = null; | |||
| _token = null; | |||
| if (!wasUnexpected) | |||
| { | |||
| _state = (int)DiscordClientState.Disconnected; | |||
| _disconnectedEvent.Set(); | |||
| } | |||
| _connectedEvent.Reset(); | |||
| } | |||
| //Helpers | |||
| @@ -16,7 +16,10 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| internal partial class VoiceWebSocket : WebSocket | |||
| { | |||
| private readonly int _targetAudioBufferLength; | |||
| private const string EncryptedMode = "xsalsa20_poly1305"; | |||
| private const string UnencryptedMode = "plain"; | |||
| private readonly int _targetAudioBufferLength; | |||
| private ManualResetEventSlim _connectWaitOnLogin; | |||
| private uint _ssrc; | |||
| private readonly Random _rand = new Random(); | |||
| @@ -26,11 +29,9 @@ namespace Discord.Net.WebSockets | |||
| private ManualResetEventSlim _sendQueueWait, _sendQueueEmptyWait; | |||
| private UdpClient _udp; | |||
| private IPEndPoint _endpoint; | |||
| private bool _isReady, _isClearing; | |||
| private bool _isClearing, _isEncrypted; | |||
| private byte[] _secretKey; | |||
| private string _myIp; | |||
| private ushort _sequence; | |||
| private string _mode; | |||
| private byte[] _encodingBuffer; | |||
| private string _serverId, _userId, _sessionId, _token; | |||
| @@ -52,24 +53,32 @@ namespace Discord.Net.WebSockets | |||
| _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames | |||
| } | |||
| public Task Login(string host, string serverId, string userId, string sessionId, string token, CancellationToken cancelToken) | |||
| public async Task Login(string host, string serverId, string userId, string sessionId, string token, CancellationToken cancelToken) | |||
| { | |||
| if (_serverId == serverId && _userId == userId && _sessionId == sessionId && _token == token) | |||
| { | |||
| //Adjust the host and tell the system to reconnect | |||
| _host = host; | |||
| await DisconnectInternal(new Exception("Server transfer occurred.")); | |||
| return; | |||
| } | |||
| _serverId = serverId; | |||
| _userId = userId; | |||
| _sessionId = sessionId; | |||
| _token = token; | |||
| return base.Connect(host, cancelToken); | |||
| await Connect(host, cancelToken); | |||
| } | |||
| protected override Task[] Run() | |||
| { | |||
| _isClearing = false; | |||
| _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | |||
| #if !DNX451 | |||
| _udp.AllowNatTraversal(true); | |||
| #endif | |||
| _isReady = false; | |||
| _isClearing = false; | |||
| VoiceCommands.Login msg = new VoiceCommands.Login(); | |||
| msg.Payload.ServerId = _serverId; | |||
| @@ -93,19 +102,24 @@ namespace Discord.Net.WebSockets | |||
| #endif | |||
| }.Concat(base.Run()).ToArray(); | |||
| } | |||
| protected override Task Cleanup() | |||
| protected override Task Cleanup(bool wasUnexpected) | |||
| { | |||
| ClearPCMFrames(); | |||
| _udp = null; | |||
| _serverId = null; | |||
| _userId = null; | |||
| _sessionId = null; | |||
| _token = null; | |||
| #if USE_THREAD | |||
| _sendThread.Join(); | |||
| _sendThread = null; | |||
| #endif | |||
| return base.Cleanup(); | |||
| ClearPCMFrames(); | |||
| if (!wasUnexpected) | |||
| { | |||
| _serverId = null; | |||
| _userId = null; | |||
| _sessionId = null; | |||
| _token = null; | |||
| } | |||
| _udp = null; | |||
| return base.Cleanup(wasUnexpected); | |||
| } | |||
| private async Task ReceiveVoiceAsync() | |||
| @@ -153,14 +167,16 @@ namespace Discord.Net.WebSockets | |||
| byte[] packet; | |||
| try | |||
| { | |||
| while (!cancelToken.IsCancellationRequested && !_isReady) | |||
| while (!cancelToken.IsCancellationRequested && _state != (int)WebSocketState.Connected) | |||
| { | |||
| #if USE_THREAD | |||
| Thread.Sleep(1); | |||
| #else | |||
| await Task.Delay(1); | |||
| #endif | |||
| } | |||
| if (cancelToken.IsCancellationRequested) | |||
| if (cancelToken.IsCancellationRequested) | |||
| return; | |||
| uint timestamp = 0; | |||
| @@ -251,15 +267,15 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| case 2: //READY | |||
| { | |||
| if (!_isReady) | |||
| if (_state != (int)WebSocketState.Connected) | |||
| { | |||
| var payload = (msg.Payload as JToken).ToObject<VoiceEvents.Ready>(); | |||
| _heartbeatInterval = payload.HeartbeatInterval; | |||
| _ssrc = payload.SSRC; | |||
| _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | |||
| //_mode = payload.Modes.LastOrDefault(); | |||
| _mode = "plain"; | |||
| _udp.Connect(_endpoint); | |||
| _isEncrypted = !payload.Modes.Contains("plain"); | |||
| _udp.Connect(_endpoint); | |||
| _sequence = (ushort)_rand.Next(0, ushort.MaxValue); | |||
| //No thread issue here because SendAsync doesn't start until _isReady is true | |||
| @@ -297,9 +313,8 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| byte[] buffer = msg.Buffer; | |||
| int length = msg.Buffer.Length; | |||
| if (!_isReady) | |||
| if (_state != (int)WebSocketState.Connected) | |||
| { | |||
| _isReady = true; | |||
| if (length != 70) | |||
| { | |||
| if (_logLevel >= LogMessageSeverity.Warning) | |||
| @@ -308,15 +323,15 @@ namespace Discord.Net.WebSockets | |||
| } | |||
| int port = buffer[68] | buffer[69] << 8; | |||
| string ip = Encoding.ASCII.GetString(buffer, 4, 70 - 6).TrimEnd('\0'); | |||
| _myIp = Encoding.ASCII.GetString(buffer, 4, 70 - 6).TrimEnd('\0'); | |||
| CompleteConnect(); | |||
| _isReady = true; | |||
| var login2 = new VoiceCommands.Login2(); | |||
| login2.Payload.Protocol = "udp"; | |||
| login2.Payload.SocketData.Address = _myIp; | |||
| login2.Payload.SocketData.Mode = _mode; | |||
| login2.Payload.SocketData.Port = port; | |||
| login2.Payload.SocketData.Address = ip; | |||
| login2.Payload.SocketData.Mode = _isEncrypted ? EncryptedMode : UnencryptedMode; | |||
| login2.Payload.SocketData.Port = port; | |||
| QueueMessage(login2); | |||
| } | |||
| else | |||
| @@ -377,8 +392,8 @@ namespace Discord.Net.WebSockets | |||
| buffer = newBuffer; | |||
| }*/ | |||
| if (_logLevel >= LogMessageSeverity.Verbose) | |||
| RaiseOnLog(LogMessageSeverity.Verbose, $"Received {buffer.Length - 12} bytes."); | |||
| if (_logLevel >= LogMessageSeverity.Debug) | |||
| RaiseOnLog(LogMessageSeverity.Debug, $"Received {buffer.Length - 12} bytes."); | |||
| //TODO: Use Voice Data | |||
| } | |||
| } | |||
| @@ -386,12 +401,6 @@ namespace Discord.Net.WebSockets | |||
| public void SendPCMFrames(byte[] data, int bytes) | |||
| { | |||
| var cancelToken = _cancelToken; | |||
| if (!_isReady || cancelToken == null) | |||
| throw new InvalidOperationException("Not connected to a voice server."); | |||
| if (bytes == 0) | |||
| return; | |||
| int frameSize = _encoder.FrameSize; | |||
| int frames = bytes / frameSize; | |||
| int expectedBytes = frames * frameSize; | |||
| @@ -431,7 +440,7 @@ namespace Discord.Net.WebSockets | |||
| int encodedLength = _encoder.EncodeFrame(data, pos, _encodingBuffer); | |||
| //TODO: Handle Encryption | |||
| if (_mode == "xsalsa20_poly1305") | |||
| if (_isEncrypted) | |||
| { | |||
| } | |||
| @@ -448,16 +457,16 @@ namespace Discord.Net.WebSockets | |||
| } | |||
| } | |||
| if (_logLevel >= LogMessageSeverity.Verbose) | |||
| RaiseOnLog(LogMessageSeverity.Verbose, $"Queued {bytes} bytes for voice output."); | |||
| if (_logLevel >= LogMessageSeverity.Debug) | |||
| RaiseOnLog(LogMessageSeverity.Debug, $"Queued {bytes} bytes for voice output."); | |||
| } | |||
| public void ClearPCMFrames() | |||
| { | |||
| _isClearing = true; | |||
| byte[] ignored; | |||
| while (_sendQueue.TryDequeue(out ignored)) { } | |||
| if (_logLevel >= LogMessageSeverity.Verbose) | |||
| RaiseOnLog(LogMessageSeverity.Verbose, "Cleared the voice buffer."); | |||
| if (_logLevel >= LogMessageSeverity.Debug) | |||
| RaiseOnLog(LogMessageSeverity.Debug, "Cleared the voice buffer."); | |||
| _isClearing = false; | |||
| } | |||
| @@ -2,13 +2,6 @@ | |||
| namespace Discord.Net.WebSockets | |||
| { | |||
| public class DisconnectedEventArgs : EventArgs | |||
| { | |||
| public readonly bool WasUnexpected; | |||
| public readonly Exception Error; | |||
| internal DisconnectedEventArgs(bool wasUnexpected, Exception error) { WasUnexpected = wasUnexpected; Error = error; } | |||
| } | |||
| internal partial class WebSocket | |||
| { | |||
| public event EventHandler Connected; | |||
| @@ -38,12 +38,14 @@ namespace Discord.Net.WebSockets | |||
| protected readonly DiscordClient _client; | |||
| protected readonly LogMessageSeverity _logLevel; | |||
| protected int _state; | |||
| protected string _host; | |||
| protected int _loginTimeout, _heartbeatInterval; | |||
| private DateTime _lastHeartbeat; | |||
| private Task _runTask; | |||
| public WebSocketState State => (WebSocketState)_state; | |||
| protected int _state; | |||
| protected ExceptionDispatchInfo _disconnectReason; | |||
| private bool _wasDisconnectUnexpected; | |||
| @@ -56,17 +58,45 @@ namespace Discord.Net.WebSockets | |||
| _client = client; | |||
| _logLevel = client.Config.LogLevel; | |||
| _loginTimeout = client.Config.ConnectionTimeout; | |||
| _cancelToken = new CancellationToken(true); | |||
| _engine = new BuiltInWebSocketEngine(client.Config.WebSocketInterval); | |||
| _engine.ProcessMessage += (s, e) => | |||
| _engine.ProcessMessage += async (s, e) => | |||
| { | |||
| if (_logLevel >= LogMessageSeverity.Debug) | |||
| RaiseOnLog(LogMessageSeverity.Debug, $"In: " + e.Message); | |||
| ProcessMessage(e.Message); | |||
| await ProcessMessage(e.Message); | |||
| }; | |||
| } | |||
| public async Task Reconnect(CancellationToken cancelToken) | |||
| { | |||
| try | |||
| { | |||
| await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||
| while (!cancelToken.IsCancellationRequested) | |||
| { | |||
| try | |||
| { | |||
| await Connect(_host, cancelToken).ConfigureAwait(false); | |||
| break; | |||
| } | |||
| catch (OperationCanceledException) { throw; } | |||
| catch (Exception ex) | |||
| { | |||
| RaiseOnLog(LogMessageSeverity.Error, $"DataSocket reconnect failed: {ex.GetBaseException().Message}"); | |||
| //Net is down? We can keep trying to reconnect until the user runs Disconnect() | |||
| await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); | |||
| } | |||
| } | |||
| } | |||
| catch (OperationCanceledException) { } | |||
| } | |||
| protected virtual async Task Connect(string host, CancellationToken cancelToken) | |||
| { | |||
| if (_state != (int)WebSocketState.Disconnected) | |||
| throw new InvalidOperationException("Client is already connected or connecting to the server."); | |||
| try | |||
| { | |||
| await Disconnect().ConfigureAwait(false); | |||
| @@ -97,19 +127,19 @@ namespace Discord.Net.WebSockets | |||
| => Connect(_host, _cancelToken);*/ | |||
| public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false); | |||
| protected async Task DisconnectInternal(Exception ex, bool isUnexpected = true, bool skipAwait = false) | |||
| protected Task DisconnectInternal(Exception ex, bool isUnexpected = true, bool skipAwait = false) | |||
| { | |||
| int oldState; | |||
| bool hasWriterLock; | |||
| //If in either connecting or connected state, get a lock by being the first to switch to disconnecting | |||
| oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connecting); | |||
| if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected | |||
| if (oldState == (int)WebSocketState.Disconnected) return TaskHelper.CompletedTask; //Already disconnected | |||
| hasWriterLock = oldState == (int)WebSocketState.Connecting; //Caused state change | |||
| if (!hasWriterLock) | |||
| { | |||
| oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connected); | |||
| if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected | |||
| if (oldState == (int)WebSocketState.Disconnected) return TaskHelper.CompletedTask; //Already disconnected | |||
| hasWriterLock = oldState == (int)WebSocketState.Connected; //Caused state change | |||
| } | |||
| @@ -121,17 +151,9 @@ namespace Discord.Net.WebSockets | |||
| } | |||
| if (!skipAwait) | |||
| { | |||
| Task task = _runTask; | |||
| if (task != null) | |||
| await task.ConfigureAwait(false); | |||
| } | |||
| if (hasWriterLock) | |||
| { | |||
| _state = (int)WebSocketState.Disconnected; | |||
| RaiseDisconnected(isUnexpected, ex); | |||
| } | |||
| return _runTask ?? TaskHelper.CompletedTask; | |||
| else | |||
| return TaskHelper.CompletedTask; | |||
| } | |||
| protected virtual async Task RunTasks() | |||
| @@ -146,9 +168,8 @@ namespace Discord.Net.WebSockets | |||
| bool wasUnexpected = _wasDisconnectUnexpected; | |||
| _wasDisconnectUnexpected = false; | |||
| await _engine.Disconnect().ConfigureAwait(false); | |||
| await Cleanup().ConfigureAwait(false); | |||
| await Cleanup(wasUnexpected).ConfigureAwait(false); | |||
| _runTask = null; | |||
| } | |||
| protected virtual Task[] Run() | |||
| @@ -158,10 +179,12 @@ namespace Discord.Net.WebSockets | |||
| .Concat(new Task[] { HeartbeatAsync(cancelToken) }) | |||
| .ToArray(); | |||
| } | |||
| protected virtual Task Cleanup() | |||
| protected virtual Task Cleanup(bool wasUnexpected) | |||
| { | |||
| _cancelTokenSource = null; | |||
| return TaskHelper.CompletedTask; | |||
| _state = (int)WebSocketState.Disconnected; | |||
| RaiseDisconnected(wasUnexpected, _disconnectReason?.SourceException); | |||
| return _engine.Disconnect(); | |||
| } | |||
| protected abstract Task ProcessMessage(string json); | |||