Brandon Smith 9 years ago
parent
commit
1773556663
20 changed files with 192 additions and 118 deletions
  1. +8
    -9
      README.md
  2. +2
    -2
      src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs
  3. +2
    -2
      src/Discord.Net.Commands/project.json
  4. +2
    -2
      src/Discord.Net.Net45/Properties/AssemblyInfo.cs
  5. +3
    -2
      src/Discord.Net/Collections/AsyncCollection.cs
  6. +2
    -2
      src/Discord.Net/Collections/Channels.cs
  7. +2
    -2
      src/Discord.Net/Collections/Members.cs
  8. +2
    -2
      src/Discord.Net/Collections/Messages.cs
  9. +2
    -2
      src/Discord.Net/Collections/Roles.cs
  10. +2
    -2
      src/Discord.Net/Collections/Servers.cs
  11. +2
    -2
      src/Discord.Net/Collections/Users.cs
  12. +2
    -1
      src/Discord.Net/DiscordClient.API.cs
  13. +29
    -14
      src/Discord.Net/DiscordClient.Voice.cs
  14. +30
    -23
      src/Discord.Net/DiscordClient.cs
  15. +1
    -1
      src/Discord.Net/DiscordClientConfig.cs
  16. +2
    -3
      src/Discord.Net/Net/WebSockets/Commands.cs
  17. +32
    -7
      src/Discord.Net/Net/WebSockets/DataWebSocket.cs
  18. +17
    -11
      src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs
  19. +49
    -28
      src/Discord.Net/Net/WebSockets/WebSocket.cs
  20. +1
    -1
      src/Discord.Net/project.json

+ 8
- 9
README.md View File

@@ -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.

+ 2
- 2
src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs View File

@@ -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")]

+ 2
- 2
src/Discord.Net.Commands/project.json View File

@@ -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": { },


+ 2
- 2
src/Discord.Net.Net45/Properties/AssemblyInfo.cs View File

@@ -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")]

+ 3
- 2
src/Discord.Net/Collections/AsyncCollection.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Collections
public abstract class AsyncCollection<TValue> : IEnumerable<TValue>
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<string, TValue> _dictionary;

protected AsyncCollection(DiscordClient client)
protected AsyncCollection(DiscordClient client, object writerLock)
{
_client = client;
_writerLock = writerLock;
_dictionary = new ConcurrentDictionary<string, TValue>();
}



+ 2
- 2
src/Discord.Net/Collections/Channels.cs View File

@@ -6,8 +6,8 @@ namespace Discord.Collections
{
public sealed class Channels : AsyncCollection<Channel>
{
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);


+ 2
- 2
src/Discord.Net/Collections/Members.cs View File

@@ -6,8 +6,8 @@ namespace Discord.Collections
{
public sealed class Members : AsyncCollection<Member>
{
internal Members(DiscordClient client)
: base(client) { }
internal Members(DiscordClient client, object writerLock)
: base(client, writerLock) { }

private string GetKey(string userId, string serverId) => serverId + '_' + userId;



+ 2
- 2
src/Discord.Net/Collections/Messages.cs View File

@@ -5,8 +5,8 @@ namespace Discord.Collections
public sealed class Messages : AsyncCollection<Message>
{
private readonly MessageCleaner _msgCleaner;
internal Messages(DiscordClient client)
: base(client)
internal Messages(DiscordClient client, object writerLock)
: base(client, writerLock)
{
_msgCleaner = new MessageCleaner(client);
}


+ 2
- 2
src/Discord.Net/Collections/Roles.cs View File

@@ -6,8 +6,8 @@ namespace Discord.Collections
{
public sealed class Roles : AsyncCollection<Role>
{
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);


+ 2
- 2
src/Discord.Net/Collections/Servers.cs View File

@@ -6,8 +6,8 @@ namespace Discord.Collections
{
public sealed class Servers : AsyncCollection<Server>
{
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);


+ 2
- 2
src/Discord.Net/Collections/Users.cs View File

@@ -6,8 +6,8 @@ namespace Discord.Collections
{
public sealed class Users : AsyncCollection<User>
{
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);


+ 2
- 1
src/Discord.Net/DiscordClient.API.cs View File

@@ -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;


+ 29
- 14
src/Discord.Net/DiscordClient.Voice.cs View File

@@ -6,28 +6,44 @@ 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);
//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()
{
CheckReady(checkVoice: true);

if (_voiceSocket.CurrentVoiceServerId != null)
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);
}
}
}

@@ -43,7 +59,6 @@ namespace Discord
_voiceSocket.SendPCMFrames(data, count);
}

/// <summary> Clears the PCM buffer. </summary>
public void ClearVoicePCM()
{
@@ -57,7 +72,7 @@ namespace Discord
{
CheckReady(checkVoice: true);

_voiceSocket.Wait();
_voiceSocket.WaitForQueue();
await TaskHelper.CompletedTask.ConfigureAwait(false);
}
}


+ 30
- 23
src/Discord.Net/DiscordClient.cs View File

@@ -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;

/// <summary> Returns the id of the current logged-in user. </summary>
public string CurrentUserId => _currentUserId;
private string _currentUserId;
@@ -95,7 +96,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);
@@ -116,7 +122,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;
@@ -131,12 +137,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)
@@ -581,11 +588,14 @@ namespace Discord
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<Events.VoiceServerUpdate>(_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;
@@ -637,9 +647,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)
@@ -748,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);
@@ -777,7 +784,7 @@ namespace Discord
_currentUserId = null;
_token = null;

if (!wasUnexpected)
if (!wasDisconnectUnexpected)
{
_state = (int)DiscordClientState.Disconnected;
_disconnectedEvent.Set();


+ 1
- 1
src/Discord.Net/DiscordClientConfig.cs View File

@@ -29,7 +29,7 @@ namespace Discord
private int _messageQueueInterval = 100;
/// <summary> 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. </summary>
public int VoiceBufferLength { get { return _voiceBufferLength; } set { SetValue(ref _voiceBufferLength, value); } }
private int _voiceBufferLength = 3000;
private int _voiceBufferLength = 1000;

//Experimental Features
#if !DNXCORE50


+ 2
- 3
src/Discord.Net/Net/WebSockets/Commands.cs View File

@@ -14,9 +14,8 @@ namespace Discord.Net.WebSockets
public sealed class KeepAlive : WebSocketMessage<ulong>
{
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<Login.Data>
{


+ 32
- 7
src/Discord.Net/Net/WebSockets/DataWebSocket.cs View File

@@ -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)
{
@@ -88,16 +112,17 @@ 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()
public void SendLeaveVoice(string serverId)
{
var leaveVoice = new Commands.JoinVoice();
leaveVoice.Payload.ServerId = serverId;
QueueMessage(leaveVoice);
}
}


+ 17
- 11
src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs View File

@@ -53,21 +53,24 @@ 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;

await Connect();
await Connect().ConfigureAwait(false);
}
public async Task Reconnect()
{
@@ -85,7 +88,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 +128,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,16 +136,15 @@ namespace Discord.Net.WebSockets
#endif

ClearPCMFrames();
if (!wasUnexpected)
if (!_wasDisconnectUnexpected)
{
_serverId = null;
_userId = null;
_sessionId = null;
_token = null;
}
_udp = null;

return base.Cleanup(wasUnexpected);
return base.Cleanup();
}

private async Task ReceiveVoiceAsync()
@@ -512,9 +514,13 @@ namespace Discord.Net.WebSockets
return new VoiceCommands.KeepAlive();
}

public void Wait()
public void WaitForQueue()
{
_sendQueueEmptyWait.Wait(_cancelToken);
}
public void WaitForConnection()
{
_sendQueueEmptyWait.Wait();
_connectedEvent.Wait();
}
}
}

+ 49
- 28
src/Discord.Net/Net/WebSockets/WebSocket.cs View File

@@ -37,30 +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;
private bool _wasDisconnectUnexpected;

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) =>
@@ -78,9 +81,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,50 +92,59 @@ 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()
{
_state = (int)WebSocketState.Connected;
_connectedEvent.Set();
RaiseConnected();
}
/*public Task Reconnect(CancellationToken cancelToken)
=> 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 +153,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 +174,23 @@ 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;
_connectedEvent.Reset();

if (disconnectState == WebSocketState.Connected)
RaiseDisconnected(wasDisconnectUnexpected, _disconnectReason?.SourceException);
}

protected abstract Task ProcessMessage(string json);


+ 1
- 1
src/Discord.Net/project.json View File

@@ -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" ],


Loading…
Cancel
Save