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