From 11cb82e0941307077ec9632dee459481f31f4996 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 16:23:10 -0300 Subject: [PATCH 1/9] Fixed SendMessage not returning message objects --- src/Discord.Net/DiscordClient.API.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 71b6a4e48..d4cdf0632 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -258,9 +258,9 @@ namespace Discord { int index = i * DiscordAPIClient.MaxMessageSize; string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); - var nonce = GenerateNonce(); if (_config.UseMessageQueue) { + var nonce = GenerateNonce(); var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _currentUserId); var currentMember = _members[msg.UserId, channel.ServerId]; msg.Update(new Net.API.Message @@ -282,7 +282,8 @@ namespace Discord var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); msg.Update(model); RaiseMessageSent(msg); - } + result[i] = msg; + } await Task.Delay(1000).ConfigureAwait(false); } return result; From d3b8b85b6f295e2c7b0934fb2fa114008e254679 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 16:25:19 -0300 Subject: [PATCH 2/9] Stop commiting broken builds >.> --- src/Discord.Net/DiscordClient.API.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index d4cdf0632..a949c38a2 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -258,9 +258,9 @@ namespace Discord { int index = i * DiscordAPIClient.MaxMessageSize; string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); + var nonce = GenerateNonce(); if (_config.UseMessageQueue) { - var nonce = GenerateNonce(); var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _currentUserId); var currentMember = _members[msg.UserId, channel.ServerId]; msg.Update(new Net.API.Message From 3570205b0f041b39420ae685bf50604e548e8db0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 19:21:08 -0300 Subject: [PATCH 3/9] Fixed reconnecting, and handling of early connection errors. --- src/Discord.Net/DiscordClient.cs | 7 ++- .../Net/WebSockets/DataWebSocket.cs | 30 ++++++++- .../Net/WebSockets/VoiceWebSocket.cs | 10 +-- src/Discord.Net/Net/WebSockets/WebSocket.cs | 63 ++++++++++++------- 4 files changed, 78 insertions(+), 32 deletions(-) diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 3c7d4da84..b5793fd49 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -95,7 +95,12 @@ namespace Discord _api = new DiscordAPIClient(_config.LogLevel, _config.APITimeout); _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 _dataSocket.Login(_token); }; + _dataSocket.Disconnected += async (s, e) => + { + RaiseDisconnected(e); + if (e.WasUnexpected) + await _dataSocket.Reconnect(_token); + }; if (_config.EnableVoice) { _voiceSocket = new VoiceWebSocket(this); diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs index 49da7bdb0..59e8ba9b8 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -20,7 +20,7 @@ namespace Discord.Net.WebSockets public async Task Login(string token) { - await Connect(); + await Connect().ConfigureAwait(false); Commands.Login msg = new Commands.Login(); msg.Payload.Token = token; @@ -29,14 +29,38 @@ namespace Discord.Net.WebSockets } private async Task Redirect(string server) { - await DisconnectInternal(isUnexpected: false); - await Connect(); + await DisconnectInternal(isUnexpected: false).ConfigureAwait(false); + await Connect().ConfigureAwait(false); var resumeMsg = new Commands.Resume(); resumeMsg.Payload.SessionId = _sessionId; resumeMsg.Payload.Sequence = _lastSeq; QueueMessage(resumeMsg); } + public async Task Reconnect(string token) + { + try + { + var cancelToken = ParentCancelToken; + await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); + while (!cancelToken.IsCancellationRequested) + { + try + { + await Login(token).ConfigureAwait(false); + break; + } + catch (OperationCanceledException) { throw; } + catch (Exception ex) + { + RaiseOnLog(LogMessageSeverity.Error, $"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 override async Task ProcessMessage(string json) { diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 51b3c8679..b39a9fc37 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -67,7 +67,7 @@ namespace Discord.Net.WebSockets _sessionId = sessionId; _token = token; - await Connect(); + await Connect().ConfigureAwait(false); } public async Task Reconnect() { @@ -85,7 +85,7 @@ namespace Discord.Net.WebSockets catch (OperationCanceledException) { throw; } catch (Exception ex) { - RaiseOnLog(LogMessageSeverity.Error, $"DataSocket reconnect failed: {ex.GetBaseException().Message}"); + RaiseOnLog(LogMessageSeverity.Error, $"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); } @@ -125,7 +125,7 @@ namespace Discord.Net.WebSockets #endif }.Concat(base.Run()).ToArray(); } - protected override Task Cleanup(bool wasUnexpected) + protected override Task Cleanup() { #if USE_THREAD _sendThread.Join(); @@ -133,7 +133,7 @@ namespace Discord.Net.WebSockets #endif ClearPCMFrames(); - if (!wasUnexpected) + if (!_wasDisconnectUnexpected) { _serverId = null; _userId = null; @@ -142,7 +142,7 @@ namespace Discord.Net.WebSockets } _udp = null; - return base.Cleanup(wasUnexpected); + return base.Cleanup(); } private async Task ReceiveVoiceAsync() diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index 6fc5e1731..6728dab4e 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -47,8 +47,9 @@ namespace Discord.Net.WebSockets public WebSocketState State => (WebSocketState)_state; protected int _state; - protected ExceptionDispatchInfo _disconnectReason; - private bool _wasDisconnectUnexpected; + protected ExceptionDispatchInfo _disconnectReason; + protected bool _wasDisconnectUnexpected; + protected WebSocketState _disconnectState; public CancellationToken ParentCancelToken { get; set; } public CancellationToken CancelToken => _cancelToken; @@ -78,9 +79,7 @@ namespace Discord.Net.WebSockets try { - await Disconnect().ConfigureAwait(false); - - _state = (int)WebSocketState.Connecting; + await Disconnect().ConfigureAwait(false); _cancelTokenSource = new CancellationTokenSource(); if (ParentCancelToken != null) @@ -91,12 +90,13 @@ namespace Discord.Net.WebSockets await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); _lastHeartbeat = DateTime.UtcNow; + _state = (int)WebSocketState.Connecting; _runTask = RunTasks(); } - catch + catch (Exception ex) { - await Disconnect().ConfigureAwait(false); - throw; + await DisconnectInternal(ex, isUnexpected: false).ConfigureAwait(false); + throw; //Dont handle this exception internally, send up it upwards } } protected void CompleteConnect() @@ -108,33 +108,40 @@ namespace Discord.Net.WebSockets => Connect(_host, _cancelToken);*/ public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false); - protected Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false) + protected async Task DisconnectInternal(Exception ex = null, 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 TaskHelper.CompletedTask; //Already disconnected + if (oldState == (int)WebSocketState.Disconnected) return; //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 TaskHelper.CompletedTask; //Already disconnected + if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected hasWriterLock = oldState == (int)WebSocketState.Connected; //Caused state change } if (hasWriterLock) { _wasDisconnectUnexpected = isUnexpected; + _disconnectState = (WebSocketState)oldState; _disconnectReason = ex != null ? ExceptionDispatchInfo.Capture(ex) : null; + + if (_disconnectState == WebSocketState.Connecting) //_runTask was never made + await Cleanup(); _cancelTokenSource.Cancel(); } if (!skipAwait) - return _runTask ?? TaskHelper.CompletedTask; + { + Task task = _runTask ?? TaskHelper.CompletedTask; + await task; + } else - return TaskHelper.CompletedTask; + await TaskHelper.CompletedTask; } protected virtual async Task RunTasks() @@ -143,19 +150,19 @@ namespace Discord.Net.WebSockets Task firstTask = Task.WhenAny(tasks); Task allTasks = Task.WhenAll(tasks); + //Wait until the first task ends/errors and capture the error try { await firstTask.ConfigureAwait(false); } catch (Exception ex) { await DisconnectInternal(ex: ex, skipAwait: true).ConfigureAwait(false); } - //When the first task ends, make sure the rest do too + //Ensure all other tasks are signaled to end. await DisconnectInternal(skipAwait: true); + + //Wait for the remaining tasks to complete try { await allTasks.ConfigureAwait(false); } catch { } - - bool wasUnexpected = _wasDisconnectUnexpected; - _wasDisconnectUnexpected = false; - await Cleanup(wasUnexpected).ConfigureAwait(false); - _runTask = null; + //Clean up state variables and raise disconnect event + await Cleanup().ConfigureAwait(false); } protected virtual Task[] Run() { @@ -164,12 +171,22 @@ namespace Discord.Net.WebSockets .Concat(new Task[] { HeartbeatAsync(cancelToken) }) .ToArray(); } - protected virtual Task Cleanup(bool wasUnexpected) + protected virtual async Task Cleanup() { + var disconnectState = _disconnectState; + _disconnectState = WebSocketState.Disconnected; + var wasDisconnectUnexpected = _wasDisconnectUnexpected; + _wasDisconnectUnexpected = false; + //Dont reset disconnectReason, we may called ThrowError() later + + await _engine.Disconnect(); _cancelTokenSource = null; - _state = (int)WebSocketState.Disconnected; - RaiseDisconnected(wasUnexpected, _disconnectReason?.SourceException); - return _engine.Disconnect(); + var oldState = _state; + _state = (int)WebSocketState.Disconnected; + _runTask = null; + + if (disconnectState == WebSocketState.Connected) + RaiseDisconnected(wasDisconnectUnexpected, _disconnectReason?.SourceException); } protected abstract Task ProcessMessage(string json); From 47fbd1efffaed5026a9933d75fffb40fc9f94def Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 21:28:49 -0300 Subject: [PATCH 4/9] Voice state improvements --- src/Discord.Net/DiscordClient.Voice.cs | 16 ++++++++-------- src/Discord.Net/DiscordClient.cs | 13 ++++++++----- src/Discord.Net/DiscordClientConfig.cs | 2 +- src/Discord.Net/Net/WebSockets/DataWebSocket.cs | 6 +++--- src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs | 10 ++++++---- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Discord.Net/DiscordClient.Voice.cs b/src/Discord.Net/DiscordClient.Voice.cs index 1d6fa39df..8e5962f1f 100644 --- a/src/Discord.Net/DiscordClient.Voice.cs +++ b/src/Discord.Net/DiscordClient.Voice.cs @@ -6,24 +6,25 @@ namespace Discord { public partial class DiscordClient { - public Task JoinVoiceServer(string channelId) - => JoinVoiceServer(_channels[channelId]); - public async Task JoinVoiceServer(Channel channel) + public Task JoinVoiceServer(Channel channel) + => JoinVoiceServer(channel.ServerId, channel.Id); + public async Task JoinVoiceServer(string serverId, string channelId) { CheckReady(checkVoice: true); - if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); await LeaveVoiceServer().ConfigureAwait(false); - _dataSocket.SendJoinVoice(channel); + _voiceSocket.SetServer(serverId); + _dataSocket.SendJoinVoice(serverId, channelId); //await _voiceSocket.WaitForConnection().ConfigureAwait(false); //TODO: Add another ManualResetSlim to wait on here, base it off of DiscordClient's setup } - public async Task LeaveVoiceServer() { CheckReady(checkVoice: true); - if (_voiceSocket.CurrentVoiceServerId != null) + if (_voiceSocket.State != Net.WebSockets.WebSocketState.Disconnected) { await _voiceSocket.Disconnect().ConfigureAwait(false); await TaskHelper.CompletedTask.ConfigureAwait(false); @@ -43,7 +44,6 @@ namespace Discord _voiceSocket.SendPCMFrames(data, count); } - /// Clears the PCM buffer. public void ClearVoicePCM() { diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index b5793fd49..8e1856cec 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -121,7 +121,7 @@ namespace Discord }; _voiceSocket.IsSpeaking += (s, e) => { - if (_voiceSocket.CurrentVoiceServerId != null) + if (_voiceSocket.State == WebSocketState.Connected) { var member = _members[e.UserId, _voiceSocket.CurrentVoiceServerId]; bool value = e.IsSpeaking; @@ -586,11 +586,14 @@ namespace Discord case "VOICE_SERVER_UPDATE": { var data = e.Payload.ToObject(_serializer); - var server = _servers[data.GuildId]; - if (_config.EnableVoice) + if (data.GuildId == _voiceSocket.CurrentVoiceServerId) { - _voiceSocket.Host = "wss://" + data.Endpoint.Split(':')[0]; - await _voiceSocket.Login(data.GuildId, _currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false); + var server = _servers[data.GuildId]; + if (_config.EnableVoice) + { + _voiceSocket.Host = "wss://" + data.Endpoint.Split(':')[0]; + await _voiceSocket.Login(_currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false); + } } } break; diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index 17bc947a3..7c623ae3e 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -29,7 +29,7 @@ namespace Discord private int _messageQueueInterval = 100; /// Gets or sets the max buffer length (in milliseconds) for outgoing voice packets. This value is the target maximum but is not guaranteed, the buffer will often go slightly above this value. public int VoiceBufferLength { get { return _voiceBufferLength; } set { SetValue(ref _voiceBufferLength, value); } } - private int _voiceBufferLength = 3000; + private int _voiceBufferLength = 1000; //Experimental Features #if !DNXCORE50 diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs index 59e8ba9b8..ccba18bd8 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -112,11 +112,11 @@ namespace Discord.Net.WebSockets return new Commands.KeepAlive(); } - public void SendJoinVoice(Channel channel) + public void SendJoinVoice(string serverId, string channelId) { var joinVoice = new Commands.JoinVoice(); - joinVoice.Payload.ServerId = channel.ServerId; - joinVoice.Payload.ChannelId = channel.Id; + joinVoice.Payload.ServerId = serverId; + joinVoice.Payload.ChannelId = channelId; QueueMessage(joinVoice); } public void SendLeaveVoice() diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index b39a9fc37..6da093bb4 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -53,16 +53,19 @@ namespace Discord.Net.WebSockets _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames } - public async Task Login(string serverId, string userId, string sessionId, string token, CancellationToken cancelToken) + public void SetServer(string serverId) { - if (_serverId == serverId && _userId == userId && _sessionId == sessionId && _token == token) + _serverId = serverId; + } + public async Task Login(string userId, string sessionId, string token, CancellationToken cancelToken) + { + if ((WebSocketState)_state != WebSocketState.Disconnected) { //Adjust the host and tell the system to reconnect await DisconnectInternal(new Exception("Server transfer occurred."), isUnexpected: false); return; } - _serverId = serverId; _userId = userId; _sessionId = sessionId; _token = token; @@ -135,7 +138,6 @@ namespace Discord.Net.WebSockets ClearPCMFrames(); if (!_wasDisconnectUnexpected) { - _serverId = null; _userId = null; _sessionId = null; _token = null; From 6e9c17143123cb4aea64780d767aa2caed62a3bf Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 22:13:08 -0300 Subject: [PATCH 5/9] Improved cache locking, removed manual timeout for Login() --- src/Discord.Net/Collections/AsyncCollection.cs | 5 +++-- src/Discord.Net/Collections/Channels.cs | 4 ++-- src/Discord.Net/Collections/Members.cs | 4 ++-- src/Discord.Net/Collections/Messages.cs | 4 ++-- src/Discord.Net/Collections/Roles.cs | 4 ++-- src/Discord.Net/Collections/Servers.cs | 4 ++-- src/Discord.Net/Collections/Users.cs | 4 ++-- src/Discord.Net/DiscordClient.cs | 16 +++++++--------- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs index 9b57d7015..c6acb6b63 100644 --- a/src/Discord.Net/Collections/AsyncCollection.cs +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -9,7 +9,7 @@ namespace Discord.Collections public abstract class AsyncCollection : IEnumerable where TValue : class { - private static readonly object _writerLock = new object(); + private readonly object _writerLock; internal class CollectionItemEventArgs : EventArgs { @@ -53,9 +53,10 @@ namespace Discord.Collections protected readonly DiscordClient _client; protected readonly ConcurrentDictionary _dictionary; - protected AsyncCollection(DiscordClient client) + protected AsyncCollection(DiscordClient client, object writerLock) { _client = client; + _writerLock = writerLock; _dictionary = new ConcurrentDictionary(); } diff --git a/src/Discord.Net/Collections/Channels.cs b/src/Discord.Net/Collections/Channels.cs index ae0e4060c..5207abb36 100644 --- a/src/Discord.Net/Collections/Channels.cs +++ b/src/Discord.Net/Collections/Channels.cs @@ -6,8 +6,8 @@ namespace Discord.Collections { public sealed class Channels : AsyncCollection { - internal Channels(DiscordClient client) - : base(client) { } + internal Channels(DiscordClient client, object writerLock) + : base(client, writerLock) { } internal Channel GetOrAdd(string id, string serverId, string recipientId = null) => GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); internal new Channel TryRemove(string id) => base.TryRemove(id); diff --git a/src/Discord.Net/Collections/Members.cs b/src/Discord.Net/Collections/Members.cs index 2ccc502f7..282915353 100644 --- a/src/Discord.Net/Collections/Members.cs +++ b/src/Discord.Net/Collections/Members.cs @@ -6,8 +6,8 @@ namespace Discord.Collections { public sealed class Members : AsyncCollection { - internal Members(DiscordClient client) - : base(client) { } + internal Members(DiscordClient client, object writerLock) + : base(client, writerLock) { } private string GetKey(string userId, string serverId) => serverId + '_' + userId; diff --git a/src/Discord.Net/Collections/Messages.cs b/src/Discord.Net/Collections/Messages.cs index 6c59a9233..2c353322b 100644 --- a/src/Discord.Net/Collections/Messages.cs +++ b/src/Discord.Net/Collections/Messages.cs @@ -5,8 +5,8 @@ namespace Discord.Collections public sealed class Messages : AsyncCollection { private readonly MessageCleaner _msgCleaner; - internal Messages(DiscordClient client) - : base(client) + internal Messages(DiscordClient client, object writerLock) + : base(client, writerLock) { _msgCleaner = new MessageCleaner(client); } diff --git a/src/Discord.Net/Collections/Roles.cs b/src/Discord.Net/Collections/Roles.cs index 8c63a2200..10557e616 100644 --- a/src/Discord.Net/Collections/Roles.cs +++ b/src/Discord.Net/Collections/Roles.cs @@ -6,8 +6,8 @@ namespace Discord.Collections { public sealed class Roles : AsyncCollection { - internal Roles(DiscordClient client) - : base(client) { } + internal Roles(DiscordClient client, object writerLock) + : base(client, writerLock) { } internal Role GetOrAdd(string id, string serverId) => GetOrAdd(id, () => new Role(_client, id, serverId)); internal new Role TryRemove(string id) => base.TryRemove(id); diff --git a/src/Discord.Net/Collections/Servers.cs b/src/Discord.Net/Collections/Servers.cs index 85e243c6e..d1f6b9245 100644 --- a/src/Discord.Net/Collections/Servers.cs +++ b/src/Discord.Net/Collections/Servers.cs @@ -6,8 +6,8 @@ namespace Discord.Collections { public sealed class Servers : AsyncCollection { - internal Servers(DiscordClient client) - : base(client) { } + internal Servers(DiscordClient client, object writerLock) + : base(client, writerLock) { } internal Server GetOrAdd(string id) => base.GetOrAdd(id, () => new Server(_client, id)); internal new Server TryRemove(string id) => base.TryRemove(id); diff --git a/src/Discord.Net/Collections/Users.cs b/src/Discord.Net/Collections/Users.cs index 56ffb060c..11f301aaf 100644 --- a/src/Discord.Net/Collections/Users.cs +++ b/src/Discord.Net/Collections/Users.cs @@ -6,8 +6,8 @@ namespace Discord.Collections { public sealed class Users : AsyncCollection { - internal Users(DiscordClient client) - : base(client) { } + internal Users(DiscordClient client, object writerLock) + : base(client, writerLock) { } internal User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); internal new User TryRemove(string id) => base.TryRemove(id); diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 8e1856cec..c3af7dcca 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -136,12 +136,13 @@ namespace Discord }; } - _channels = new Channels(this); - _members = new Members(this); - _messages = new Messages(this); - _roles = new Roles(this); - _servers = new Servers(this); - _users = new Users(this); + object cacheLock = new object(); + _channels = new Channels(this, cacheLock); + _members = new Members(this, cacheLock); + _messages = new Messages(this, cacheLock); + _roles = new Roles(this, cacheLock); + _servers = new Servers(this, cacheLock); + _users = new Users(this, cacheLock); _dataSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message); if (_config.EnableVoice) @@ -645,9 +646,6 @@ namespace Discord string token; try { - var cancelToken = new CancellationTokenSource(); - cancelToken.CancelAfter(5000); - _api.CancelToken = cancelToken.Token; var response = await _api.Login(email, password).ConfigureAwait(false); token = response.Token; if (_config.LogLevel >= LogMessageSeverity.Verbose) From 7fa998a7c80d0f58a4e059e28df4708cc6af14a9 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 22:16:08 -0300 Subject: [PATCH 6/9] Minor tweak --- src/Discord.Net/Net/WebSockets/Commands.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net/Net/WebSockets/Commands.cs b/src/Discord.Net/Net/WebSockets/Commands.cs index 2fe053c92..00a725a6c 100644 --- a/src/Discord.Net/Net/WebSockets/Commands.cs +++ b/src/Discord.Net/Net/WebSockets/Commands.cs @@ -14,9 +14,8 @@ namespace Discord.Net.WebSockets public sealed class KeepAlive : WebSocketMessage { public KeepAlive() : base(1, GetTimestamp()) { } - private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - private static ulong GetTimestamp() - => (ulong)(DateTime.UtcNow - epoch).TotalMilliseconds; + private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static ulong GetTimestamp() => (ulong)(DateTime.UtcNow - epoch).TotalMilliseconds; } public sealed class Login : WebSocketMessage { From 454bd6f43d69c7fa9043262f00c3d68eda827df4 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 22:42:25 -0300 Subject: [PATCH 7/9] Voice stability patches --- src/Discord.Net/DiscordClient.Voice.cs | 31 ++++++++++++++----- src/Discord.Net/DiscordClient.cs | 17 +++++----- .../Net/WebSockets/DataWebSocket.cs | 3 +- .../Net/WebSockets/VoiceWebSocket.cs | 8 +++-- src/Discord.Net/Net/WebSockets/WebSocket.cs | 20 +++++++----- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/Discord.Net/DiscordClient.Voice.cs b/src/Discord.Net/DiscordClient.Voice.cs index 8e5962f1f..3a434a99e 100644 --- a/src/Discord.Net/DiscordClient.Voice.cs +++ b/src/Discord.Net/DiscordClient.Voice.cs @@ -15,10 +15,22 @@ namespace Discord if (channelId == null) throw new ArgumentNullException(nameof(channelId)); await LeaveVoiceServer().ConfigureAwait(false); - _voiceSocket.SetServer(serverId); - _dataSocket.SendJoinVoice(serverId, channelId); - //await _voiceSocket.WaitForConnection().ConfigureAwait(false); - //TODO: Add another ManualResetSlim to wait on here, base it off of DiscordClient's setup + + try + { + await Task.Run(() => + { + _voiceSocket.SetServer(serverId); + _dataSocket.SendJoinVoice(serverId, channelId); + _voiceSocket.WaitForConnection(); + }) + .Timeout(_config.ConnectionTimeout) + .ConfigureAwait(false); + } + catch (TaskCanceledException) + { + await LeaveVoiceServer().ConfigureAwait(false); + } } public async Task LeaveVoiceServer() { @@ -26,9 +38,12 @@ namespace Discord if (_voiceSocket.State != Net.WebSockets.WebSocketState.Disconnected) { - await _voiceSocket.Disconnect().ConfigureAwait(false); - await TaskHelper.CompletedTask.ConfigureAwait(false); - _dataSocket.SendLeaveVoice(); + var serverId = _voiceSocket.CurrentVoiceServerId; + if (serverId != null) + { + await _voiceSocket.Disconnect().ConfigureAwait(false); + _dataSocket.SendLeaveVoice(serverId); + } } } @@ -57,7 +72,7 @@ namespace Discord { CheckReady(checkVoice: true); - _voiceSocket.Wait(); + _voiceSocket.WaitForQueue(); await TaskHelper.CompletedTask.ConfigureAwait(false); } } diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index c3af7dcca..b13933267 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -32,11 +32,12 @@ namespace Discord private readonly ManualResetEvent _disconnectedEvent; private readonly ManualResetEventSlim _connectedEvent; private readonly JsonSerializer _serializer; - protected ExceptionDispatchInfo _disconnectReason; private Task _runTask; - private bool _wasDisconnectUnexpected; private string _token; + protected ExceptionDispatchInfo _disconnectReason; + private bool _wasDisconnectUnexpected; + /// Returns the id of the current logged-in user. public string CurrentUserId => _currentUserId; private string _currentUserId; @@ -754,14 +755,14 @@ namespace Discord //When the first task ends, make sure the rest do too await DisconnectInternal(skipAwait: true); - bool wasUnexpected = _wasDisconnectUnexpected; - _wasDisconnectUnexpected = false; - - await Cleanup(wasUnexpected).ConfigureAwait(false); + await Cleanup().ConfigureAwait(false); _runTask = null; } - private async Task Cleanup(bool wasUnexpected) + private async Task Cleanup() { + var wasDisconnectUnexpected = _wasDisconnectUnexpected; + _wasDisconnectUnexpected = false; + await _dataSocket.Disconnect().ConfigureAwait(false); if (_config.EnableVoice) await _voiceSocket.Disconnect().ConfigureAwait(false); @@ -783,7 +784,7 @@ namespace Discord _currentUserId = null; _token = null; - if (!wasUnexpected) + if (!wasDisconnectUnexpected) { _state = (int)DiscordClientState.Disconnected; _disconnectedEvent.Set(); diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs index ccba18bd8..2f5effb9c 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs @@ -119,9 +119,10 @@ namespace Discord.Net.WebSockets joinVoice.Payload.ChannelId = channelId; QueueMessage(joinVoice); } - public void SendLeaveVoice() + public void SendLeaveVoice(string serverId) { var leaveVoice = new Commands.JoinVoice(); + leaveVoice.Payload.ServerId = serverId; QueueMessage(leaveVoice); } } diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 6da093bb4..52f76a3c2 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -514,9 +514,13 @@ namespace Discord.Net.WebSockets return new VoiceCommands.KeepAlive(); } - public void Wait() + public void WaitForQueue() { - _sendQueueEmptyWait.Wait(); + _sendQueueEmptyWait.Wait(_cancelToken); + } + public void WaitForConnection() + { + _connectedEvent.Wait(); } } } diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index 6728dab4e..aa12279c8 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -37,31 +37,33 @@ namespace Discord.Net.WebSockets protected readonly IWebSocketEngine _engine; protected readonly DiscordClient _client; protected readonly LogMessageSeverity _logLevel; + protected readonly ManualResetEventSlim _connectedEvent; - public string Host { get; set; } + protected ExceptionDispatchInfo _disconnectReason; + protected bool _wasDisconnectUnexpected; + protected WebSocketState _disconnectState; protected int _loginTimeout, _heartbeatInterval; private DateTime _lastHeartbeat; private Task _runTask; - public WebSocketState State => (WebSocketState)_state; - protected int _state; - - protected ExceptionDispatchInfo _disconnectReason; - protected bool _wasDisconnectUnexpected; - protected WebSocketState _disconnectState; - public CancellationToken ParentCancelToken { get; set; } public CancellationToken CancelToken => _cancelToken; private CancellationTokenSource _cancelTokenSource; protected CancellationToken _cancelToken; + public string Host { get; set; } + + public WebSocketState State => (WebSocketState)_state; + protected int _state; + public WebSocket(DiscordClient client) { _client = client; _logLevel = client.Config.LogLevel; _loginTimeout = client.Config.ConnectionTimeout; _cancelToken = new CancellationToken(true); + _connectedEvent = new ManualResetEventSlim(false); _engine = new BuiltInWebSocketEngine(client.Config.WebSocketInterval); _engine.ProcessMessage += async (s, e) => @@ -102,6 +104,7 @@ namespace Discord.Net.WebSockets protected void CompleteConnect() { _state = (int)WebSocketState.Connected; + _connectedEvent.Set(); RaiseConnected(); } /*public Task Reconnect(CancellationToken cancelToken) @@ -184,6 +187,7 @@ namespace Discord.Net.WebSockets var oldState = _state; _state = (int)WebSocketState.Disconnected; _runTask = null; + _connectedEvent.Reset(); if (disconnectState == WebSocketState.Connected) RaiseDisconnected(wasDisconnectUnexpected, _disconnectReason?.SourceException); From 34a1550b8c3857caf6a4890f8f6bf2726520cd23 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 22:55:35 -0300 Subject: [PATCH 8/9] 0.7.0 --- src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs | 4 ++-- src/Discord.Net.Commands/project.json | 4 ++-- src/Discord.Net.Net45/Properties/AssemblyInfo.cs | 4 ++-- src/Discord.Net/project.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs index c99664f41..21df14995 100644 --- a/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] -[assembly: AssemblyVersion("0.6.1.2")] -[assembly: AssemblyFileVersion("0.6.1.2")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index 9b1c708e5..239e00ee2 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "0.7.0-beta1", + "version": "0.7.0", "description": "A small Discord.Net extension to make bot creation easier.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -13,7 +13,7 @@ "warningsAsErrors": true }, "dependencies": { - "Discord.Net": "0.7.0-beta1" + "Discord.Net": "0.7.0" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs index 5222f5d02..24dc6a56e 100644 --- a/src/Discord.Net.Net45/Properties/AssemblyInfo.cs +++ b/src/Discord.Net.Net45/Properties/AssemblyInfo.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] -[assembly: AssemblyVersion("0.6.1.2")] -[assembly: AssemblyFileVersion("0.6.1.2")] +[assembly: AssemblyVersion("0.7.0.0")] +[assembly: AssemblyFileVersion("0.7.0.0")] diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 919a264e6..69ab0527e 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,5 +1,5 @@ { - "version": "0.7.0-beta1", + "version": "0.7.0", "description": "An unofficial .Net API wrapper for the Discord client.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], From 5c71e3f99b66da7df41f3a05fb54495e8215b302 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 23 Sep 2015 23:11:33 -0300 Subject: [PATCH 9/9] Updated readme --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e56886c2b..537c8c2d2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Discord.Net v0.7.0-beta1 +# Discord.Net v0.7.0 An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). [Join the discussion](https://discord.gg/0SBTUU1wZTVjAMPx) on Discord. @@ -7,11 +7,13 @@ An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). The Discord API is still in active development, meaning this library may break at any time without notice. Discord.Net itself is also in alpha so several functions may be unstable or not work at all. -### Features -- Server Management (Servers, Channels, Messages, Invites) -- User Moderation (Kick/Ban/Unban/Mute/Unmute/Deafen/Undeafen) -- Alpha Voice Support (Outgoing only currently) +### Current Features +- Using Discord API version 3 - Supports .Net 4.5 and DNX 4.5.1 +- Server Management (Servers, Channels, Messages, Invites, Roles, Users) +- Send/Receieve Messages (Including mentions and formatting) +- Basic Voice Support (Outgoing only, Unencrypted only) +- Command extension library (Supports permission levels) ### NuGet Packages - [Discord.Net](https://www.nuget.org/packages/Discord.Net/) @@ -26,7 +28,6 @@ client.MessageCreated += async (s, e) => await client.SendMessage(e.Message.ChannelId, e.Message.Text); }; await client.Connect("discordtest@email.com", "Password123"); -await client.AcceptInvite("channel-invite-code"); ``` ### Example (Command Client) @@ -48,11 +49,9 @@ client.CreateCommand("acceptinvite") } }); await client.Connect("discordtest@email.com", "Password123"); -await client.AcceptInvite("channel-invite-code"); - ``` ### Known Issues - Due to current Discord restrictions, private messages are blocked unless both the sender and recipient are members of the same server. -- Caches do not currently clean up when their entries are no longer referenced, and there is no cap to the message cache. For now, disconencting and reconnecting will clear all caches. +- The Message caches does not currently clean up when their entries are no longer referenced, and there is currently no cap to it. For now, disconnecting and reconnecting will clear all caches. - DNX Core 5.0 is experiencing several network-related issues and support has been temporarily dropped. \ No newline at end of file