Reworked internal task engine for DiscordClient and WebSocket. Several other minor async fixes.
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
using Discord.API.Converters;
|
using Discord.API.Converters;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Discord.Audio.API
|
namespace Discord.API
|
||||||
{
|
{
|
||||||
internal sealed class VoiceServerUpdateEvent
|
internal sealed class VoiceServerUpdateEvent
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ namespace Discord.Audio
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var logger = Client.Log().CreateLogger("Voice");
|
var logger = Client.Log().CreateLogger("Voice");
|
||||||
var voiceSocket = new VoiceWebSocket(Client.Config, _config, logger);
|
_defaultClient = new DiscordAudioClient(this, 0, logger, _client.WebSocket);
|
||||||
_defaultClient = new DiscordAudioClient(this, 0, logger, _client.WebSocket, voiceSocket);
|
|
||||||
}
|
}
|
||||||
_talkingUsers = new ConcurrentDictionary<User, bool>();
|
_talkingUsers = new ConcurrentDictionary<User, bool>();
|
||||||
|
|
||||||
@@ -145,27 +144,26 @@ namespace Discord.Audio
|
|||||||
return Task.FromResult(_defaultClient);
|
return Task.FromResult(_defaultClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = _voiceClients.GetOrAdd(server.Id, (Func<long, DiscordAudioClient>)(_ =>
|
var client = _voiceClients.GetOrAdd(server.Id, _ =>
|
||||||
{
|
{
|
||||||
int id = unchecked(++_nextClientId);
|
int id = unchecked(++_nextClientId);
|
||||||
var logger = Client.Log().CreateLogger($"Voice #{id}");
|
var logger = Client.Log().CreateLogger($"Voice #{id}");
|
||||||
Net.WebSockets.GatewaySocket gatewaySocket = null;
|
GatewaySocket gatewaySocket = null;
|
||||||
var voiceSocket = new VoiceWebSocket(Client.Config, _config, logger);
|
var voiceClient = new DiscordAudioClient(this, id, logger, gatewaySocket);
|
||||||
var voiceClient = new DiscordAudioClient((AudioService)(this), (int)id, (Logger)logger, (Net.WebSockets.GatewaySocket)gatewaySocket, (VoiceWebSocket)voiceSocket);
|
|
||||||
voiceClient.SetServerId(server.Id);
|
voiceClient.SetServerId(server.Id);
|
||||||
|
|
||||||
voiceSocket.OnPacket += (s, e) =>
|
voiceClient.VoiceSocket.OnPacket += (s, e) =>
|
||||||
{
|
{
|
||||||
RaiseOnPacket(e);
|
RaiseOnPacket(e);
|
||||||
};
|
};
|
||||||
voiceSocket.IsSpeaking += (s, e) =>
|
voiceClient.VoiceSocket.IsSpeaking += (s, e) =>
|
||||||
{
|
{
|
||||||
var user = Client.GetUser(server, e.UserId);
|
var user = Client.GetUser(server, e.UserId);
|
||||||
RaiseUserIsSpeakingUpdated(user, e.IsSpeaking);
|
RaiseUserIsSpeakingUpdated(user, e.IsSpeaking);
|
||||||
};
|
};
|
||||||
|
|
||||||
return voiceClient;
|
return voiceClient;
|
||||||
}));
|
});
|
||||||
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
|
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
|
||||||
return Task.FromResult(client);
|
return Task.FromResult(client);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Discord.Net.WebSockets;
|
using Discord.API;
|
||||||
|
using Discord.Net.WebSockets;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -10,22 +11,29 @@ namespace Discord.Audio
|
|||||||
public int Id => _id;
|
public int Id => _id;
|
||||||
|
|
||||||
private readonly AudioService _service;
|
private readonly AudioService _service;
|
||||||
private readonly GatewaySocket _gatewaySocket;
|
|
||||||
private readonly VoiceWebSocket _voiceSocket;
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public long? ServerId => _voiceSocket.ServerId;
|
public GatewaySocket GatewaySocket => _gatewaySocket;
|
||||||
public long? ChannelId => _voiceSocket.ChannelId;
|
private readonly GatewaySocket _gatewaySocket;
|
||||||
|
|
||||||
public DiscordAudioClient(AudioService service, int id, Logger logger, GatewaySocket gatewaySocket, VoiceWebSocket voiceSocket)
|
public VoiceWebSocket VoiceSocket => _voiceSocket;
|
||||||
|
private readonly VoiceWebSocket _voiceSocket;
|
||||||
|
|
||||||
|
public string Token => _token;
|
||||||
|
private string _token;
|
||||||
|
|
||||||
|
public long? ServerId => _voiceSocket.ServerId;
|
||||||
|
public long? ChannelId => _voiceSocket.ChannelId;
|
||||||
|
|
||||||
|
public DiscordAudioClient(AudioService service, int id, Logger logger, GatewaySocket gatewaySocket)
|
||||||
{
|
{
|
||||||
_service = service;
|
_service = service;
|
||||||
_id = id;
|
_id = id;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_gatewaySocket = gatewaySocket;
|
_gatewaySocket = gatewaySocket;
|
||||||
_voiceSocket = voiceSocket;
|
_voiceSocket = new VoiceWebSocket(service.Client, this, logger);
|
||||||
|
|
||||||
/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
|
/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
|
||||||
_voiceSocket.Disconnected += async (s, e) =>
|
_voiceSocket.Disconnected += async (s, e) =>
|
||||||
{
|
{
|
||||||
_voiceSocket.CurrentServerId;
|
_voiceSocket.CurrentServerId;
|
||||||
@@ -37,7 +45,7 @@ namespace Discord.Audio
|
|||||||
await socket.Reconnect().ConfigureAwait(false);
|
await socket.Reconnect().ConfigureAwait(false);
|
||||||
};*/
|
};*/
|
||||||
|
|
||||||
/*_voiceSocket.IsSpeaking += (s, e) =>
|
/*_voiceSocket.IsSpeaking += (s, e) =>
|
||||||
{
|
{
|
||||||
if (_voiceSocket.State == WebSocketState.Connected)
|
if (_voiceSocket.State == WebSocketState.Connected)
|
||||||
{
|
{
|
||||||
@@ -54,27 +62,28 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
};*/
|
};*/
|
||||||
|
|
||||||
/*this.Connected += (s, e) =>
|
/*this.Connected += (s, e) =>
|
||||||
{
|
{
|
||||||
_voiceSocket.ParentCancelToken = _cancelToken;
|
_voiceSocket.ParentCancelToken = _cancelToken;
|
||||||
};*/
|
};*/
|
||||||
|
|
||||||
_gatewaySocket.ReceivedDispatch += async (s, e) =>
|
_gatewaySocket.ReceivedDispatch += async (s, e) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (e.Type)
|
switch (e.Type)
|
||||||
{
|
{
|
||||||
case "VOICE_SERVER_UPDATE":
|
case "VOICE_SERVER_UPDATE":
|
||||||
{
|
{
|
||||||
long serverId = IdConvert.ToLong(e.Payload.Value<string>("guild_id"));
|
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_gatewaySocket.Serializer);
|
||||||
|
long serverId = data.ServerId;
|
||||||
|
|
||||||
if (serverId == ServerId)
|
if (serverId == ServerId)
|
||||||
{
|
{
|
||||||
var client = _service.Client;
|
var client = _service.Client;
|
||||||
string token = e.Payload.Value<string>("token");
|
_token = data.Token;
|
||||||
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
|
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
|
||||||
await _voiceSocket.Connect(client.CurrentUser.Id, _gatewaySocket.SessionId, token/*, client.CancelToken*/).ConfigureAwait(false);
|
await _voiceSocket.Connect().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ namespace Discord.Net.WebSockets
|
|||||||
//private readonly Random _rand;
|
//private readonly Random _rand;
|
||||||
private readonly int _targetAudioBufferLength;
|
private readonly int _targetAudioBufferLength;
|
||||||
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders;
|
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders;
|
||||||
private readonly AudioServiceConfig _audioConfig;
|
private readonly DiscordAudioClient _audioClient;
|
||||||
|
private readonly AudioServiceConfig _config;
|
||||||
private OpusEncoder _encoder;
|
private OpusEncoder _encoder;
|
||||||
private uint _ssrc;
|
private uint _ssrc;
|
||||||
private ConcurrentDictionary<uint, long> _ssrcMapping;
|
private ConcurrentDictionary<uint, long> _ssrcMapping;
|
||||||
@@ -37,8 +38,8 @@ namespace Discord.Net.WebSockets
|
|||||||
private bool _isEncrypted;
|
private bool _isEncrypted;
|
||||||
private byte[] _secretKey, _encodingBuffer;
|
private byte[] _secretKey, _encodingBuffer;
|
||||||
private ushort _sequence;
|
private ushort _sequence;
|
||||||
private long? _serverId, _channelId, _userId;
|
private long? _serverId, _channelId;
|
||||||
private string _sessionId, _token, _encryptionMode;
|
private string _encryptionMode;
|
||||||
private int _ping;
|
private int _ping;
|
||||||
|
|
||||||
private Thread _sendThread, _receiveThread;
|
private Thread _sendThread, _receiveThread;
|
||||||
@@ -48,24 +49,21 @@ namespace Discord.Net.WebSockets
|
|||||||
public int Ping => _ping;
|
public int Ping => _ping;
|
||||||
internal VoiceBuffer OutputBuffer => _sendBuffer;
|
internal VoiceBuffer OutputBuffer => _sendBuffer;
|
||||||
|
|
||||||
public VoiceWebSocket(DiscordConfig config, AudioServiceConfig audioConfig, Logger logger)
|
public VoiceWebSocket(DiscordClient client, DiscordAudioClient audioClient, Logger logger)
|
||||||
: base(config, logger)
|
: base(client, logger)
|
||||||
{
|
{
|
||||||
_audioConfig = audioConfig;
|
_audioClient = audioClient;
|
||||||
_decoders = new ConcurrentDictionary<uint, OpusDecoder>();
|
_config = client.Audio().Config;
|
||||||
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
|
_decoders = new ConcurrentDictionary<uint, OpusDecoder>();
|
||||||
|
_targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames
|
||||||
_encodingBuffer = new byte[MaxOpusSize];
|
_encodingBuffer = new byte[MaxOpusSize];
|
||||||
_ssrcMapping = new ConcurrentDictionary<uint, long>();
|
_ssrcMapping = new ConcurrentDictionary<uint, long>();
|
||||||
_encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.Audio);
|
_encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.Audio);
|
||||||
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
|
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Connect(long userId, string sessionId, string token)
|
public async Task Connect()
|
||||||
{
|
{
|
||||||
_userId = userId;
|
|
||||||
_sessionId = sessionId;
|
|
||||||
_token = token;
|
|
||||||
|
|
||||||
await BeginConnect().ConfigureAwait(false);
|
await BeginConnect().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public async Task Reconnect()
|
public async Task Reconnect()
|
||||||
@@ -73,12 +71,12 @@ namespace Discord.Net.WebSockets
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cancelToken = ParentCancelToken.Value;
|
var cancelToken = ParentCancelToken.Value;
|
||||||
await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false);
|
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
|
||||||
while (!cancelToken.IsCancellationRequested)
|
while (!cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Connect(_userId.Value, _sessionId, _token).ConfigureAwait(false);
|
await Connect().ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { throw; }
|
catch (OperationCanceledException) { throw; }
|
||||||
@@ -86,29 +84,26 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
_logger.Error("Reconnect failed", ex);
|
_logger.Error("Reconnect failed", ex);
|
||||||
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
||||||
await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
|
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
}
|
}
|
||||||
public Task Disconnect()
|
public Task Disconnect() => _taskManager.Stop();
|
||||||
{
|
|
||||||
return SignalDisconnect(wait: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task Run()
|
protected override async Task Run()
|
||||||
{
|
{
|
||||||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
|
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
|
||||||
|
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
if ((_audioConfig.Mode & AudioMode.Outgoing) != 0)
|
if ((_config.Mode & AudioMode.Outgoing) != 0)
|
||||||
{
|
{
|
||||||
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_cancelToken)));
|
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_cancelToken)));
|
||||||
_sendThread.IsBackground = true;
|
_sendThread.IsBackground = true;
|
||||||
_sendThread.Start();
|
_sendThread.Start();
|
||||||
}
|
}
|
||||||
if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
|
if ((_config.Mode & AudioMode.Incoming) != 0)
|
||||||
{
|
{
|
||||||
_receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(_cancelToken)));
|
_receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(_cancelToken)));
|
||||||
_receiveThread.IsBackground = true;
|
_receiveThread.IsBackground = true;
|
||||||
@@ -120,9 +115,9 @@ namespace Discord.Net.WebSockets
|
|||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
tasks.Add(WatcherAsync());
|
tasks.Add(WatcherAsync());
|
||||||
#endif
|
#endif
|
||||||
await RunTasks(tasks.ToArray());
|
tasks.AddRange(_engine.GetTasks(_cancelToken));
|
||||||
|
tasks.Add(HeartbeatAsync(_cancelToken));
|
||||||
await Cleanup();
|
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
protected override Task Cleanup()
|
protected override Task Cleanup()
|
||||||
{
|
{
|
||||||
@@ -141,12 +136,6 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClearPCMFrames();
|
ClearPCMFrames();
|
||||||
if (!_wasDisconnectUnexpected)
|
|
||||||
{
|
|
||||||
_userId = null;
|
|
||||||
_sessionId = null;
|
|
||||||
_token = null;
|
|
||||||
}
|
|
||||||
_udp = null;
|
_udp = null;
|
||||||
|
|
||||||
return base.Cleanup();
|
return base.Cleanup();
|
||||||
@@ -161,7 +150,7 @@ namespace Discord.Net.WebSockets
|
|||||||
int packetLength, resultOffset, resultLength;
|
int packetLength, resultOffset, resultLength;
|
||||||
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);
|
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
|
||||||
if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
|
if ((_config.Mode & AudioMode.Incoming) != 0)
|
||||||
{
|
{
|
||||||
decodingBuffer = new byte[MaxOpusSize];
|
decodingBuffer = new byte[MaxOpusSize];
|
||||||
nonce = new byte[24];
|
nonce = new byte[24];
|
||||||
@@ -188,7 +177,7 @@ namespace Discord.Net.WebSockets
|
|||||||
|
|
||||||
if (packetLength > 0 && endpoint.Equals(_endpoint))
|
if (packetLength > 0 && endpoint.Equals(_endpoint))
|
||||||
{
|
{
|
||||||
if (_state != (int)WebSocketState.Connected)
|
if (_state != (int)ConnectionState.Connected)
|
||||||
{
|
{
|
||||||
if (packetLength != 70)
|
if (packetLength != 70)
|
||||||
return;
|
return;
|
||||||
@@ -197,8 +186,8 @@ namespace Discord.Net.WebSockets
|
|||||||
int port = packet[68] | packet[69] << 8;
|
int port = packet[68] | packet[69] << 8;
|
||||||
|
|
||||||
SendSelectProtocol(ip, port);
|
SendSelectProtocol(ip, port);
|
||||||
if ((_audioConfig.Mode & AudioMode.Incoming) == 0)
|
if ((_config.Mode & AudioMode.Incoming) == 0)
|
||||||
return;
|
return; //We dont need this thread anymore
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -258,7 +247,7 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancelToken.IsCancellationRequested && _state != (int)WebSocketState.Connected)
|
while (!cancelToken.IsCancellationRequested && _state != (int)ConnectionState.Connected)
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
|
|
||||||
if (cancelToken.IsCancellationRequested)
|
if (cancelToken.IsCancellationRequested)
|
||||||
@@ -410,14 +399,15 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
case VoiceOpCodes.Ready:
|
case VoiceOpCodes.Ready:
|
||||||
{
|
{
|
||||||
if (_state != (int)WebSocketState.Connected)
|
if (_state != (int)ConnectionState.Connected)
|
||||||
{
|
{
|
||||||
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(_serializer);
|
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(_serializer);
|
||||||
_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);
|
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault();
|
||||||
|
_endpoint = new IPEndPoint(address, payload.Port);
|
||||||
|
|
||||||
if (_audioConfig.EnableEncryption)
|
if (_config.EnableEncryption)
|
||||||
{
|
{
|
||||||
if (payload.Modes.Contains(EncryptedMode))
|
if (payload.Modes.Contains(EncryptedMode))
|
||||||
{
|
{
|
||||||
@@ -458,7 +448,7 @@ namespace Discord.Net.WebSockets
|
|||||||
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_serializer);
|
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_serializer);
|
||||||
_secretKey = payload.SecretKey;
|
_secretKey = payload.SecretKey;
|
||||||
SendIsTalking(true);
|
SendIsTalking(true);
|
||||||
await EndConnect();
|
EndConnect();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case VoiceOpCodes.Speaking:
|
case VoiceOpCodes.Speaking:
|
||||||
@@ -507,9 +497,9 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
var msg = new IdentifyCommand();
|
var msg = new IdentifyCommand();
|
||||||
msg.Payload.ServerId = _serverId.Value;
|
msg.Payload.ServerId = _serverId.Value;
|
||||||
msg.Payload.SessionId = _sessionId;
|
msg.Payload.SessionId = _client.SessionId;
|
||||||
msg.Payload.Token = _token;
|
msg.Payload.Token = _audioClient.Token;
|
||||||
msg.Payload.UserId = _userId.Value;
|
msg.Payload.UserId = _client.UserId.Value;
|
||||||
QueueMessage(msg);
|
QueueMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
internal void SetRunFunc(Action<CommandEventArgs> func)
|
internal void SetRunFunc(Action<CommandEventArgs> func)
|
||||||
{
|
{
|
||||||
_runFunc = e => { func(e); return TaskHelper.CompletedTask; };
|
_runFunc = TaskHelper.ToAsync(func);
|
||||||
}
|
}
|
||||||
internal Task Run(CommandEventArgs args)
|
internal Task Run(CommandEventArgs args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,8 +68,8 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Discord.Net\API\Converters\LongCollectionConverter.cs">
|
<Compile Include="..\Discord.Net\API\Converters\LongStringCollectionConverter.cs">
|
||||||
<Link>API\Converters\LongCollectionConverter.cs</Link>
|
<Link>API\Converters\LongStringCollectionConverter.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\API\Converters\LongStringConverter.cs">
|
<Compile Include="..\Discord.Net\API\Converters\LongStringConverter.cs">
|
||||||
<Link>API\Converters\LongStringConverter.cs</Link>
|
<Link>API\Converters\LongStringConverter.cs</Link>
|
||||||
@@ -77,12 +77,12 @@
|
|||||||
<Compile Include="..\Discord.Net\API\Endpoints.cs">
|
<Compile Include="..\Discord.Net\API\Endpoints.cs">
|
||||||
<Link>API\Endpoints.cs</Link>
|
<Link>API\Endpoints.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\API\Enums\AvatarImageType.cs">
|
|
||||||
<Link>API\Enums\AvatarImageType.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Discord.Net\API\Enums\ChannelType.cs">
|
<Compile Include="..\Discord.Net\API\Enums\ChannelType.cs">
|
||||||
<Link>API\Enums\ChannelType.cs</Link>
|
<Link>API\Enums\ChannelType.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net\API\Enums\ImageType.cs">
|
||||||
|
<Link>API\Enums\ImageType.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs">
|
<Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs">
|
||||||
<Link>API\Enums\PermissionTarget.cs</Link>
|
<Link>API\Enums\PermissionTarget.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -176,6 +176,9 @@
|
|||||||
<Compile Include="..\Discord.Net\Helpers\Reference.cs">
|
<Compile Include="..\Discord.Net\Helpers\Reference.cs">
|
||||||
<Link>Helpers\Reference.cs</Link>
|
<Link>Helpers\Reference.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net\Helpers\TaskManager.cs">
|
||||||
|
<Link>Helpers\TaskManager.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\Models\Channel.cs">
|
<Compile Include="..\Discord.Net\Models\Channel.cs">
|
||||||
<Link>Models\Channel.cs</Link>
|
<Link>Models\Channel.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
@@ -7,7 +8,26 @@ namespace Discord
|
|||||||
public static Task CompletedTask { get; }
|
public static Task CompletedTask { get; }
|
||||||
static TaskHelper()
|
static TaskHelper()
|
||||||
{
|
{
|
||||||
|
#if DOTNET54
|
||||||
|
CompletedTask = Task.CompletedTask;
|
||||||
|
#else
|
||||||
CompletedTask = Task.Delay(0);
|
CompletedTask = Task.Delay(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<Task> ToAsync(Action action)
|
||||||
|
{
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
action(); return CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static Func<T, Task> ToAsync<T>(Action<T> action)
|
||||||
|
{
|
||||||
|
return x =>
|
||||||
|
{
|
||||||
|
action(x); return CompletedTask;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Discord.API.Converters
|
namespace Discord.API.Converters
|
||||||
{
|
{
|
||||||
public class EnumerableLongStringConverter : JsonConverter
|
public class LongStringEnumerableConverter : JsonConverter
|
||||||
{
|
{
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
@@ -38,7 +38,7 @@ namespace Discord.API.Converters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LongArrayStringConverter : JsonConverter
|
internal class LongStringArrayConverter : JsonConverter
|
||||||
{
|
{
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ namespace Discord.API
|
|||||||
[JsonProperty("joined_at")]
|
[JsonProperty("joined_at")]
|
||||||
public DateTime? JoinedAt;
|
public DateTime? JoinedAt;
|
||||||
[JsonProperty("roles")]
|
[JsonProperty("roles")]
|
||||||
[JsonConverter(typeof(LongArrayStringConverter))]
|
[JsonConverter(typeof(LongStringArrayConverter))]
|
||||||
public long[] Roles;
|
public long[] Roles;
|
||||||
}
|
}
|
||||||
public class ExtendedMemberInfo : MemberInfo
|
public class ExtendedMemberInfo : MemberInfo
|
||||||
@@ -53,7 +53,7 @@ namespace Discord.API
|
|||||||
[JsonProperty("status")]
|
[JsonProperty("status")]
|
||||||
public string Status;
|
public string Status;
|
||||||
[JsonProperty("roles")] //TODO: Might be temporary
|
[JsonProperty("roles")] //TODO: Might be temporary
|
||||||
[JsonConverter(typeof(LongArrayStringConverter))]
|
[JsonConverter(typeof(LongStringArrayConverter))]
|
||||||
public long[] Roles;
|
public long[] Roles;
|
||||||
}
|
}
|
||||||
public class VoiceMemberInfo : MemberReference
|
public class VoiceMemberInfo : MemberReference
|
||||||
@@ -88,7 +88,7 @@ namespace Discord.API
|
|||||||
[JsonConverter(typeof(NullableLongStringConverter))]
|
[JsonConverter(typeof(NullableLongStringConverter))]
|
||||||
public long? ChannelId;
|
public long? ChannelId;
|
||||||
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
[JsonConverter(typeof(EnumerableLongStringConverter))]
|
[JsonConverter(typeof(LongStringEnumerableConverter))]
|
||||||
public IEnumerable<long> Roles;
|
public IEnumerable<long> Roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ namespace Discord.API
|
|||||||
[JsonProperty("content")]
|
[JsonProperty("content")]
|
||||||
public string Content;
|
public string Content;
|
||||||
[JsonProperty("mentions")]
|
[JsonProperty("mentions")]
|
||||||
[JsonConverter(typeof(EnumerableLongStringConverter))]
|
[JsonConverter(typeof(LongStringEnumerableConverter))]
|
||||||
public IEnumerable<long> Mentions;
|
public IEnumerable<long> Mentions;
|
||||||
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string Nonce;
|
public string Nonce;
|
||||||
@@ -123,7 +123,7 @@ namespace Discord.API
|
|||||||
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string Content;
|
public string Content;
|
||||||
[JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
[JsonConverter(typeof(EnumerableLongStringConverter))]
|
[JsonConverter(typeof(LongStringEnumerableConverter))]
|
||||||
public IEnumerable<long> Mentions;
|
public IEnumerable<long> Mentions;
|
||||||
}
|
}
|
||||||
public sealed class EditMessageResponse : MessageInfo { }
|
public sealed class EditMessageResponse : MessageInfo { }
|
||||||
|
|||||||
@@ -380,10 +380,11 @@ namespace Discord
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _api.EditMessage(
|
await _api.EditMessage(
|
||||||
msg.Id,
|
msg.Id,
|
||||||
msg.Channel.Id,
|
msg.Channel.Id,
|
||||||
queuedMessage.Text,
|
queuedMessage.Text,
|
||||||
queuedMessage.MentionedUsers);
|
queuedMessage.MentionedUsers)
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (WebException) { break; }
|
catch (WebException) { break; }
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Discord
|
|||||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||||
CheckReady();
|
CheckReady();
|
||||||
|
|
||||||
try { await _api.DeleteRole(role.Server.Id, role.Id); }
|
try { await _api.DeleteRole(role.Server.Id, role.Id).ConfigureAwait(false); }
|
||||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
CheckReady();
|
CheckReady();
|
||||||
|
|
||||||
return (await _api.GetVoiceRegions()).Select(x => new Region { Id = x.Id, Name = x.Name, Hostname = x.Hostname, Port = x.Port });
|
var regions = await _api.GetVoiceRegions().ConfigureAwait(false);
|
||||||
|
return regions.Select(x => new Region { Id = x.Id, Name = x.Name, Hostname = x.Hostname, Port = x.Port });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ namespace Discord
|
|||||||
if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days));
|
if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days));
|
||||||
CheckReady();
|
CheckReady();
|
||||||
|
|
||||||
var response = await _api.PruneUsers(server.Id, days, simulate);
|
var response = await _api.PruneUsers(server.Id, days, simulate).ConfigureAwait(false);
|
||||||
return response.Pruned ?? 0;
|
return response.Pruned ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,11 +275,11 @@ namespace Discord
|
|||||||
|
|
||||||
await _api.EditProfile(currentPassword: currentPassword,
|
await _api.EditProfile(currentPassword: currentPassword,
|
||||||
username: username ?? _privateUser?.Name, email: email ?? _privateUser?.Global.Email, password: password,
|
username: username ?? _privateUser?.Name, email: email ?? _privateUser?.Global.Email, password: password,
|
||||||
avatar: avatar, avatarType: avatarType, existingAvatar: _privateUser?.AvatarId);
|
avatar: avatar, avatarType: avatarType, existingAvatar: _privateUser?.AvatarId).ConfigureAwait(false);
|
||||||
|
|
||||||
if (password != null)
|
if (password != null)
|
||||||
{
|
{
|
||||||
var loginResponse = await _api.Login(_privateUser.Global.Email, password);
|
var loginResponse = await _api.Login(_privateUser.Global.Email, password).ConfigureAwait(false);
|
||||||
_api.Token = loginResponse.Token;
|
_api.Token = loginResponse.Token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
public enum DiscordClientState : byte
|
public enum ConnectionState : byte
|
||||||
{
|
{
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Connecting,
|
Connecting,
|
||||||
Connected,
|
Connected,
|
||||||
Disconnecting
|
Disconnecting
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DisconnectedEventArgs : EventArgs
|
public class DisconnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public readonly bool WasUnexpected;
|
public readonly bool WasUnexpected;
|
||||||
public readonly Exception Error;
|
public readonly Exception Error;
|
||||||
@@ -51,25 +51,24 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
public static readonly string Version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3);
|
public static readonly string Version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3);
|
||||||
|
|
||||||
private readonly ManualResetEvent _disconnectedEvent;
|
private readonly LogService _log;
|
||||||
|
private readonly Logger _logger, _restLogger, _cacheLogger;
|
||||||
|
private readonly Dictionary<Type, object> _singletons;
|
||||||
|
private readonly object _cacheLock;
|
||||||
|
private readonly Semaphore _lock;
|
||||||
|
private readonly ManualResetEvent _disconnectedEvent;
|
||||||
private readonly ManualResetEventSlim _connectedEvent;
|
private readonly ManualResetEventSlim _connectedEvent;
|
||||||
private readonly Dictionary<Type, object> _singletons;
|
private readonly TaskManager _taskManager;
|
||||||
private readonly LogService _log;
|
|
||||||
private readonly object _cacheLock;
|
|
||||||
private Logger _logger, _restLogger, _cacheLogger;
|
|
||||||
private bool _sentInitialLog;
|
private bool _sentInitialLog;
|
||||||
private UserStatus _status;
|
private UserStatus _status;
|
||||||
private int? _gameId;
|
private int? _gameId;
|
||||||
private Task _runTask;
|
|
||||||
private ExceptionDispatchInfo _disconnectReason;
|
|
||||||
private bool _wasDisconnectUnexpected;
|
|
||||||
|
|
||||||
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary>
|
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary>
|
||||||
public DiscordConfig Config => _config;
|
public DiscordConfig Config => _config;
|
||||||
private readonly DiscordConfig _config;
|
private readonly DiscordConfig _config;
|
||||||
|
|
||||||
/// <summary> Returns the current connection state of this client. </summary>
|
/// <summary> Returns the current connection state of this client. </summary>
|
||||||
public DiscordClientState State => (DiscordClientState)_state;
|
public ConnectionState State => (ConnectionState)_state;
|
||||||
private int _state;
|
private int _state;
|
||||||
|
|
||||||
/// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary>
|
/// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary>
|
||||||
@@ -82,12 +81,17 @@ namespace Discord
|
|||||||
|
|
||||||
public string GatewayUrl => _gateway;
|
public string GatewayUrl => _gateway;
|
||||||
private string _gateway;
|
private string _gateway;
|
||||||
|
|
||||||
public string Token => _token;
|
public string Token => _token;
|
||||||
private string _token;
|
private string _token;
|
||||||
|
|
||||||
/// <summary> Returns a cancellation token that triggers when the client is manually disconnected. </summary>
|
public string SessionId => _sessionId;
|
||||||
public CancellationToken CancelToken => _cancelToken;
|
private string _sessionId;
|
||||||
|
|
||||||
|
public long? UserId => _privateUser?.Id;
|
||||||
|
|
||||||
|
/// <summary> Returns a cancellation token that triggers when the client is manually disconnected. </summary>
|
||||||
|
public CancellationToken CancelToken => _cancelToken;
|
||||||
private CancellationTokenSource _cancelTokenSource;
|
private CancellationTokenSource _cancelTokenSource;
|
||||||
private CancellationToken _cancelToken;
|
private CancellationToken _cancelToken;
|
||||||
|
|
||||||
@@ -111,35 +115,37 @@ namespace Discord
|
|||||||
_config.Lock();
|
_config.Lock();
|
||||||
|
|
||||||
_nonceRand = new Random();
|
_nonceRand = new Random();
|
||||||
_state = (int)DiscordClientState.Disconnected;
|
_state = (int)ConnectionState.Disconnected;
|
||||||
_status = UserStatus.Online;
|
_status = UserStatus.Online;
|
||||||
|
|
||||||
//Services
|
//Services
|
||||||
_singletons = new Dictionary<Type, object>();
|
_singletons = new Dictionary<Type, object>();
|
||||||
_log = AddService(new LogService());
|
_log = AddService(new LogService());
|
||||||
CreateMainLogger();
|
_logger = CreateMainLogger();
|
||||||
|
|
||||||
//Async
|
//Async
|
||||||
|
_lock = new Semaphore(1, 1);
|
||||||
|
_taskManager = new TaskManager(Cleanup);
|
||||||
_cancelToken = new CancellationToken(true);
|
_cancelToken = new CancellationToken(true);
|
||||||
_disconnectedEvent = new ManualResetEvent(true);
|
_disconnectedEvent = new ManualResetEvent(true);
|
||||||
_connectedEvent = new ManualResetEventSlim(false);
|
_connectedEvent = new ManualResetEventSlim(false);
|
||||||
|
|
||||||
//Cache
|
//Cache
|
||||||
_cacheLock = new object();
|
_cacheLock = new object();
|
||||||
_channels = new Channels(this, _cacheLock);
|
_channels = new Channels(this, _cacheLock);
|
||||||
_users = new Users(this, _cacheLock);
|
_users = new Users(this, _cacheLock);
|
||||||
_messages = new Messages(this, _cacheLock, Config.MessageCacheSize > 0);
|
_messages = new Messages(this, _cacheLock, Config.MessageCacheSize > 0);
|
||||||
_roles = new Roles(this, _cacheLock);
|
_roles = new Roles(this, _cacheLock);
|
||||||
_servers = new Servers(this, _cacheLock);
|
_servers = new Servers(this, _cacheLock);
|
||||||
_globalUsers = new GlobalUsers(this, _cacheLock);
|
_globalUsers = new GlobalUsers(this, _cacheLock);
|
||||||
CreateCacheLogger();
|
_cacheLogger = CreateCacheLogger();
|
||||||
|
|
||||||
//Networking
|
//Networking
|
||||||
_webSocket = new GatewaySocket(_config, _log.CreateLogger("WebSocket"));
|
_webSocket = new GatewaySocket(this, _log.CreateLogger("WebSocket"));
|
||||||
var settings = new JsonSerializerSettings();
|
var settings = new JsonSerializerSettings();
|
||||||
_webSocket.Connected += (s, e) =>
|
_webSocket.Connected += (s, e) =>
|
||||||
{
|
{
|
||||||
if (_state == (int)DiscordClientState.Connecting)
|
if (_state == (int)ConnectionState.Connecting)
|
||||||
EndConnect();
|
EndConnect();
|
||||||
};
|
};
|
||||||
_webSocket.Disconnected += (s, e) =>
|
_webSocket.Disconnected += (s, e) =>
|
||||||
@@ -157,88 +163,94 @@ namespace Discord
|
|||||||
_api.CancelToken = _cancelToken;
|
_api.CancelToken = _cancelToken;
|
||||||
await SendStatus().ConfigureAwait(false);
|
await SendStatus().ConfigureAwait(false);
|
||||||
};
|
};
|
||||||
CreateRestLogger();
|
_restLogger = CreateRestLogger();
|
||||||
|
|
||||||
//Import/Export
|
//Import/Export
|
||||||
_messageImporter = new JsonSerializer();
|
_messageImporter = new JsonSerializer();
|
||||||
_messageImporter.ContractResolver = new Message.ImportResolver();
|
_messageImporter.ContractResolver = new Message.ImportResolver();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateMainLogger()
|
private Logger CreateMainLogger()
|
||||||
{
|
{
|
||||||
_logger = _log.CreateLogger("Client");
|
Logger logger = null;
|
||||||
if (_logger.Level >= LogSeverity.Info)
|
if (_log.Level >= LogSeverity.Info)
|
||||||
{
|
{
|
||||||
JoinedServer += (s, e) => _logger.Info($"Server Created: {e.Server?.Name ?? "[Private]"}");
|
logger = _log.CreateLogger("Client");
|
||||||
LeftServer += (s, e) => _logger.Info($"Server Destroyed: {e.Server?.Name ?? "[Private]"}");
|
JoinedServer += (s, e) => logger.Info($"Server Created: {e.Server?.Name ?? "[Private]"}");
|
||||||
ServerUpdated += (s, e) => _logger.Info($"Server Updated: {e.Server?.Name ?? "[Private]"}");
|
LeftServer += (s, e) => logger.Info($"Server Destroyed: {e.Server?.Name ?? "[Private]"}");
|
||||||
ServerAvailable += (s, e) => _logger.Info($"Server Available: {e.Server?.Name ?? "[Private]"}");
|
ServerUpdated += (s, e) => logger.Info($"Server Updated: {e.Server?.Name ?? "[Private]"}");
|
||||||
ServerUnavailable += (s, e) => _logger.Info($"Server Unavailable: {e.Server?.Name ?? "[Private]"}");
|
ServerAvailable += (s, e) => logger.Info($"Server Available: {e.Server?.Name ?? "[Private]"}");
|
||||||
ChannelCreated += (s, e) => _logger.Info($"Channel Created: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}");
|
ServerUnavailable += (s, e) => logger.Info($"Server Unavailable: {e.Server?.Name ?? "[Private]"}");
|
||||||
ChannelDestroyed += (s, e) => _logger.Info($"Channel Destroyed: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}");
|
ChannelCreated += (s, e) => logger.Info($"Channel Created: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}");
|
||||||
ChannelUpdated += (s, e) => _logger.Info($"Channel Updated: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}");
|
ChannelDestroyed += (s, e) => logger.Info($"Channel Destroyed: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}");
|
||||||
MessageReceived += (s, e) => _logger.Info($"Message Received: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
ChannelUpdated += (s, e) => logger.Info($"Channel Updated: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}");
|
||||||
MessageDeleted += (s, e) => _logger.Info($"Message Deleted: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
MessageReceived += (s, e) => logger.Info($"Message Received: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
||||||
MessageUpdated += (s, e) => _logger.Info($"Message Update: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
MessageDeleted += (s, e) => logger.Info($"Message Deleted: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
||||||
RoleCreated += (s, e) => _logger.Info($"Role Created: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}");
|
MessageUpdated += (s, e) => logger.Info($"Message Update: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
||||||
RoleUpdated += (s, e) => _logger.Info($"Role Updated: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}");
|
RoleCreated += (s, e) => logger.Info($"Role Created: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}");
|
||||||
RoleDeleted += (s, e) => _logger.Info($"Role Deleted: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}");
|
RoleUpdated += (s, e) => logger.Info($"Role Updated: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}");
|
||||||
UserBanned += (s, e) => _logger.Info($"Banned User: {e.Server?.Name ?? "[Private]" }/{e.UserId}");
|
RoleDeleted += (s, e) => logger.Info($"Role Deleted: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}");
|
||||||
UserUnbanned += (s, e) => _logger.Info($"Unbanned User: {e.Server?.Name ?? "[Private]"}/{e.UserId}");
|
UserBanned += (s, e) => logger.Info($"Banned User: {e.Server?.Name ?? "[Private]" }/{e.UserId}");
|
||||||
UserJoined += (s, e) => _logger.Info($"User Joined: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
UserUnbanned += (s, e) => logger.Info($"Unbanned User: {e.Server?.Name ?? "[Private]"}/{e.UserId}");
|
||||||
UserLeft += (s, e) => _logger.Info($"User Left: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
UserJoined += (s, e) => logger.Info($"User Joined: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
||||||
UserUpdated += (s, e) => _logger.Info($"User Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
UserLeft += (s, e) => logger.Info($"User Left: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
||||||
UserVoiceStateUpdated += (s, e) => _logger.Info($"Voice Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
UserUpdated += (s, e) => logger.Info($"User Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
||||||
ProfileUpdated += (s, e) => _logger.Info("Profile Updated");
|
UserVoiceStateUpdated += (s, e) => logger.Info($"Voice Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Name}");
|
||||||
|
ProfileUpdated += (s, e) => logger.Info("Profile Updated");
|
||||||
}
|
}
|
||||||
if (_log.Level >= LogSeverity.Verbose)
|
if (_log.Level >= LogSeverity.Verbose)
|
||||||
{
|
{
|
||||||
UserIsTypingUpdated += (s, e) => _logger.Verbose($"Is Typing: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.User?.Name}");
|
UserIsTypingUpdated += (s, e) => logger.Verbose($"Is Typing: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.User?.Name}");
|
||||||
MessageAcknowledged += (s, e) => _logger.Verbose($"Ack Message: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
MessageAcknowledged += (s, e) => logger.Verbose($"Ack Message: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
||||||
MessageSent += (s, e) => _logger.Verbose($"Sent Message: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
MessageSent += (s, e) => logger.Verbose($"Sent Message: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}");
|
||||||
UserPresenceUpdated += (s, e) => _logger.Verbose($"Presence Updated: {e.Server?.Name ?? "[Private]"}/{e.User?.Name}");
|
UserPresenceUpdated += (s, e) => logger.Verbose($"Presence Updated: {e.Server?.Name ?? "[Private]"}/{e.User?.Name}");
|
||||||
}
|
}
|
||||||
|
return logger;
|
||||||
}
|
}
|
||||||
private void CreateRestLogger()
|
private Logger CreateRestLogger()
|
||||||
{
|
{
|
||||||
_restLogger = _log.CreateLogger("Rest");
|
Logger logger = null;
|
||||||
if (_log.Level >= LogSeverity.Verbose)
|
if (_log.Level >= LogSeverity.Verbose)
|
||||||
{
|
{
|
||||||
_api.RestClient.OnRequest += (s, e) =>
|
logger = _log.CreateLogger("Rest");
|
||||||
|
_api.RestClient.OnRequest += (s, e) =>
|
||||||
{
|
{
|
||||||
if (e.Payload != null)
|
if (e.Payload != null)
|
||||||
_restLogger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})");
|
logger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})");
|
||||||
else
|
else
|
||||||
_restLogger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms");
|
logger.Verbose( $"{e.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
return logger;
|
||||||
private void CreateCacheLogger()
|
}
|
||||||
{
|
private Logger CreateCacheLogger()
|
||||||
_cacheLogger = _log.CreateLogger("Cache");
|
{
|
||||||
if (_log.Level >= LogSeverity.Debug)
|
Logger logger = null;
|
||||||
{
|
if (_log.Level >= LogSeverity.Debug)
|
||||||
_channels.ItemCreated += (s, e) => _cacheLogger.Debug( $"Created Channel {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
{
|
||||||
_channels.ItemDestroyed += (s, e) => _cacheLogger.Debug( $"Destroyed Channel {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
logger = _log.CreateLogger("Cache");
|
||||||
_channels.Cleared += (s, e) => _cacheLogger.Debug( $"Cleared Channels");
|
_channels.ItemCreated += (s, e) => logger.Debug( $"Created Channel {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
||||||
_users.ItemCreated += (s, e) => _cacheLogger.Debug( $"Created User {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
_channels.ItemDestroyed += (s, e) => logger.Debug( $"Destroyed Channel {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
||||||
_users.ItemDestroyed += (s, e) => _cacheLogger.Debug( $"Destroyed User {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
_channels.Cleared += (s, e) => logger.Debug( $"Cleared Channels");
|
||||||
_users.Cleared += (s, e) => _cacheLogger.Debug( $"Cleared Users");
|
_users.ItemCreated += (s, e) => logger.Debug( $"Created User {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
||||||
_messages.ItemCreated += (s, e) => _cacheLogger.Debug( $"Created Message {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}");
|
_users.ItemDestroyed += (s, e) => logger.Debug( $"Destroyed User {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
||||||
_messages.ItemDestroyed += (s, e) => _cacheLogger.Debug( $"Destroyed Message {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}");
|
_users.Cleared += (s, e) => logger.Debug( $"Cleared Users");
|
||||||
_messages.ItemRemapped += (s, e) => _cacheLogger.Debug( $"Remapped Message {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Channel.Id}/[{e.OldId} -> {e.NewId}]");
|
_messages.ItemCreated += (s, e) => logger.Debug( $"Created Message {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}");
|
||||||
_messages.Cleared += (s, e) => _cacheLogger.Debug( $"Cleared Messages");
|
_messages.ItemDestroyed += (s, e) => logger.Debug( $"Destroyed Message {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}");
|
||||||
_roles.ItemCreated += (s, e) => _cacheLogger.Debug( $"Created Role {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
_messages.ItemRemapped += (s, e) => logger.Debug( $"Remapped Message {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Channel.Id}/[{e.OldId} -> {e.NewId}]");
|
||||||
_roles.ItemDestroyed += (s, e) => _cacheLogger.Debug( $"Destroyed Role {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
_messages.Cleared += (s, e) => logger.Debug( $"Cleared Messages");
|
||||||
_roles.Cleared += (s, e) => _cacheLogger.Debug( $"Cleared Roles");
|
_roles.ItemCreated += (s, e) => logger.Debug( $"Created Role {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
||||||
_servers.ItemCreated += (s, e) => _cacheLogger.Debug( $"Created Server {e.Item.Id}");
|
_roles.ItemDestroyed += (s, e) => logger.Debug( $"Destroyed Role {IdConvert.ToString(e.Item.Server?.Id) ?? "[Private]"}/{e.Item.Id}");
|
||||||
_servers.ItemDestroyed += (s, e) => _cacheLogger.Debug( $"Destroyed Server {e.Item.Id}");
|
_roles.Cleared += (s, e) => logger.Debug( $"Cleared Roles");
|
||||||
_servers.Cleared += (s, e) => _cacheLogger.Debug( $"Cleared Servers");
|
_servers.ItemCreated += (s, e) => logger.Debug( $"Created Server {e.Item.Id}");
|
||||||
_globalUsers.ItemCreated += (s, e) => _cacheLogger.Debug( $"Created User {e.Item.Id}");
|
_servers.ItemDestroyed += (s, e) => logger.Debug( $"Destroyed Server {e.Item.Id}");
|
||||||
_globalUsers.ItemDestroyed += (s, e) => _cacheLogger.Debug( $"Destroyed User {e.Item.Id}");
|
_servers.Cleared += (s, e) => logger.Debug( $"Cleared Servers");
|
||||||
_globalUsers.Cleared += (s, e) => _cacheLogger.Debug( $"Cleared Users");
|
_globalUsers.ItemCreated += (s, e) => logger.Debug( $"Created User {e.Item.Id}");
|
||||||
}
|
_globalUsers.ItemDestroyed += (s, e) => logger.Debug( $"Destroyed User {e.Item.Id}");
|
||||||
}
|
_globalUsers.Cleared += (s, e) => logger.Debug( $"Cleared Users");
|
||||||
|
}
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Connects to the Discord server with the provided email and password. </summary>
|
/// <summary> Connects to the Discord server with the provided email and password. </summary>
|
||||||
/// <returns> Returns a token for future connections. </returns>
|
/// <returns> Returns a token for future connections. </returns>
|
||||||
@@ -247,17 +259,16 @@ namespace Discord
|
|||||||
if (!_sentInitialLog)
|
if (!_sentInitialLog)
|
||||||
SendInitialLog();
|
SendInitialLog();
|
||||||
|
|
||||||
if (State != DiscordClientState.Disconnected)
|
if (State != ConnectionState.Disconnected)
|
||||||
await Disconnect().ConfigureAwait(false);
|
await Disconnect().ConfigureAwait(false);
|
||||||
|
|
||||||
var response = await _api.Login(email, password)
|
var response = await _api.Login(email, password).ConfigureAwait(false);
|
||||||
.ConfigureAwait(false);
|
|
||||||
_token = response.Token;
|
_token = response.Token;
|
||||||
_api.Token = response.Token;
|
_api.Token = response.Token;
|
||||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||||
_logger.Verbose( "Login successful, got token.");
|
_logger.Verbose( "Login successful, got token.");
|
||||||
|
|
||||||
await BeginConnect();
|
await BeginConnect().ConfigureAwait(false);
|
||||||
return response.Token;
|
return response.Token;
|
||||||
}
|
}
|
||||||
/// <summary> Connects to the Discord server with the provided token. </summary>
|
/// <summary> Connects to the Discord server with the provided token. </summary>
|
||||||
@@ -266,48 +277,62 @@ namespace Discord
|
|||||||
if (!_sentInitialLog)
|
if (!_sentInitialLog)
|
||||||
SendInitialLog();
|
SendInitialLog();
|
||||||
|
|
||||||
if (State != (int)DiscordClientState.Disconnected)
|
if (State != (int)ConnectionState.Disconnected)
|
||||||
await Disconnect().ConfigureAwait(false);
|
await Disconnect().ConfigureAwait(false);
|
||||||
|
|
||||||
_token = token;
|
_token = token;
|
||||||
_api.Token = token;
|
_api.Token = token;
|
||||||
await BeginConnect();
|
await BeginConnect().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task BeginConnect()
|
private async Task BeginConnect()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_state = (int)DiscordClientState.Connecting;
|
_lock.WaitOne();
|
||||||
|
|
||||||
var gatewayResponse = await _api.Gateway().ConfigureAwait(false);
|
|
||||||
string gateway = gatewayResponse.Url;
|
|
||||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
|
||||||
_logger.Verbose( $"Websocket endpoint: {gateway}");
|
|
||||||
|
|
||||||
_disconnectedEvent.Reset();
|
|
||||||
|
|
||||||
_gateway = gateway;
|
|
||||||
|
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
|
||||||
_cancelToken = _cancelTokenSource.Token;
|
|
||||||
|
|
||||||
_webSocket.Host = gateway;
|
|
||||||
_webSocket.ParentCancelToken = _cancelToken;
|
|
||||||
await _webSocket.Connect(_token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
_runTask = RunTasks();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Cancel if either Disconnect is called, data socket errors or timeout is reached
|
await _taskManager.Stop().ConfigureAwait(false);
|
||||||
var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _webSocket.CancelToken).Token;
|
_state = (int)ConnectionState.Connecting;
|
||||||
_connectedEvent.Wait(cancelToken);
|
|
||||||
|
var gatewayResponse = await _api.Gateway().ConfigureAwait(false);
|
||||||
|
string gateway = gatewayResponse.Url;
|
||||||
|
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||||
|
_logger.Verbose( $"Websocket endpoint: {gateway}");
|
||||||
|
|
||||||
|
_disconnectedEvent.Reset();
|
||||||
|
|
||||||
|
_gateway = gateway;
|
||||||
|
|
||||||
|
_cancelTokenSource = new CancellationTokenSource();
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
|
|
||||||
|
_webSocket.Host = gateway;
|
||||||
|
_webSocket.ParentCancelToken = _cancelToken;
|
||||||
|
await _webSocket.Connect().ConfigureAwait(false);
|
||||||
|
|
||||||
|
List<Task> tasks = new List<Task>();
|
||||||
|
tasks.Add(_cancelToken.Wait());
|
||||||
|
if (_config.UseMessageQueue)
|
||||||
|
tasks.Add(MessageQueueAsync());
|
||||||
|
|
||||||
|
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Cancel if either Disconnect is called, data socket errors or timeout is reached
|
||||||
|
var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _webSocket.CancelToken).Token;
|
||||||
|
_connectedEvent.Wait(cancelToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_webSocket.ThrowError(); //Throws data socket's internal error if any occured
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
finally
|
||||||
{
|
{
|
||||||
_webSocket.ThrowError(); //Throws data socket's internal error if any occured
|
_lock.Release();
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -318,88 +343,15 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
private void EndConnect()
|
private void EndConnect()
|
||||||
{
|
{
|
||||||
_state = (int)DiscordClientState.Connected;
|
_state = (int)ConnectionState.Connected;
|
||||||
_connectedEvent.Set();
|
_connectedEvent.Set();
|
||||||
RaiseConnected();
|
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() => SignalDisconnect(new Exception("Disconnect was requested by user."), isUnexpected: false);
|
public Task Disconnect() => _taskManager.Stop();
|
||||||
private async Task SignalDisconnect(Exception ex = null, bool isUnexpected = true, bool wait = false)
|
|
||||||
{
|
private async Task Cleanup()
|
||||||
int oldState;
|
|
||||||
bool hasWriterLock;
|
|
||||||
|
|
||||||
//If in either connecting or connected state, get a lock by being the first to switch to disconnecting
|
|
||||||
oldState = Interlocked.CompareExchange(ref _state, (int)DiscordClientState.Disconnecting, (int)DiscordClientState.Connecting);
|
|
||||||
if (oldState == (int)DiscordClientState.Disconnected) return; //Already disconnected
|
|
||||||
hasWriterLock = oldState == (int)DiscordClientState.Connecting; //Caused state change
|
|
||||||
if (!hasWriterLock)
|
|
||||||
{
|
|
||||||
oldState = Interlocked.CompareExchange(ref _state, (int)DiscordClientState.Disconnecting, (int)DiscordClientState.Connected);
|
|
||||||
if (oldState == (int)DiscordClientState.Disconnected) return; //Already disconnected
|
|
||||||
hasWriterLock = oldState == (int)DiscordClientState.Connected; //Caused state change
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasWriterLock)
|
|
||||||
{
|
|
||||||
_wasDisconnectUnexpected = isUnexpected;
|
|
||||||
_disconnectReason = ex != null ? ExceptionDispatchInfo.Capture(ex) : null;
|
|
||||||
|
|
||||||
_cancelTokenSource.Cancel();
|
|
||||||
/*if (_disconnectState == DiscordClientState.Connecting) //_runTask was never made
|
|
||||||
await Cleanup().ConfigureAwait(false);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wait)
|
|
||||||
{
|
|
||||||
Task task = _runTask;
|
|
||||||
if (_runTask != null)
|
|
||||||
await task.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RunTasks()
|
|
||||||
{
|
|
||||||
List<Task> tasks = new List<Task>();
|
|
||||||
tasks.Add(_cancelToken.Wait());
|
|
||||||
if (_config.UseMessageQueue)
|
|
||||||
tasks.Add(MessageQueueAsync());
|
|
||||||
|
|
||||||
Task[] tasksArray = tasks.ToArray();
|
|
||||||
Task firstTask = Task.WhenAny(tasksArray);
|
|
||||||
Task allTasks = Task.WhenAll(tasksArray);
|
|
||||||
|
|
||||||
//Wait until the first task ends/errors and capture the error
|
|
||||||
try { await firstTask.ConfigureAwait(false); }
|
|
||||||
catch (Exception ex) { await SignalDisconnect(ex: ex, wait: true).ConfigureAwait(false); }
|
|
||||||
|
|
||||||
//Ensure all other tasks are signaled to end.
|
|
||||||
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
|
||||||
|
|
||||||
//Wait for the remaining tasks to complete
|
|
||||||
try { await allTasks.ConfigureAwait(false); }
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
//Start cleanup
|
|
||||||
var wasDisconnectUnexpected = _wasDisconnectUnexpected;
|
|
||||||
_wasDisconnectUnexpected = false;
|
|
||||||
|
|
||||||
await _webSocket.SignalDisconnect().ConfigureAwait(false);
|
|
||||||
|
|
||||||
_privateUser = null;
|
|
||||||
_gateway = null;
|
|
||||||
_token = null;
|
|
||||||
|
|
||||||
if (!wasDisconnectUnexpected)
|
|
||||||
{
|
|
||||||
_state = (int)DiscordClientState.Disconnected;
|
|
||||||
_disconnectedEvent.Set();
|
|
||||||
}
|
|
||||||
_connectedEvent.Reset();
|
|
||||||
_runTask = null;
|
|
||||||
}
|
|
||||||
private async Task Stop()
|
|
||||||
{
|
{
|
||||||
if (Config.UseMessageQueue)
|
if (Config.UseMessageQueue)
|
||||||
{
|
{
|
||||||
@@ -417,7 +369,13 @@ namespace Discord
|
|||||||
_globalUsers.Clear();
|
_globalUsers.Clear();
|
||||||
|
|
||||||
_privateUser = null;
|
_privateUser = null;
|
||||||
}
|
_gateway = null;
|
||||||
|
_token = null;
|
||||||
|
|
||||||
|
_state = (int)ConnectionState.Disconnected;
|
||||||
|
_disconnectedEvent.Set();
|
||||||
|
_connectedEvent.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnReceivedEvent(WebSocketEventEventArgs e)
|
private void OnReceivedEvent(WebSocketEventEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -429,7 +387,8 @@ namespace Discord
|
|||||||
case "READY": //Resync
|
case "READY": //Resync
|
||||||
{
|
{
|
||||||
var data = e.Payload.ToObject<ReadyEvent>(_webSocket.Serializer);
|
var data = e.Payload.ToObject<ReadyEvent>(_webSocket.Serializer);
|
||||||
_privateUser = _users.GetOrAdd(data.User.Id, null);
|
_sessionId = data.SessionId;
|
||||||
|
_privateUser = _users.GetOrAdd(data.User.Id, null);
|
||||||
_privateUser.Update(data.User);
|
_privateUser.Update(data.User);
|
||||||
_privateUser.Global.Update(data.User);
|
_privateUser.Global.Update(data.User);
|
||||||
foreach (var model in data.Guilds)
|
foreach (var model in data.Guilds)
|
||||||
@@ -863,11 +822,11 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
switch (_state)
|
switch (_state)
|
||||||
{
|
{
|
||||||
case (int)DiscordClientState.Disconnecting:
|
case (int)ConnectionState.Disconnecting:
|
||||||
throw new InvalidOperationException("The client is disconnecting.");
|
throw new InvalidOperationException("The client is disconnecting.");
|
||||||
case (int)DiscordClientState.Disconnected:
|
case (int)ConnectionState.Disconnected:
|
||||||
throw new InvalidOperationException("The client is not connected to Discord");
|
throw new InvalidOperationException("The client is not connected to Discord");
|
||||||
case (int)DiscordClientState.Connecting:
|
case (int)ConnectionState.Connecting:
|
||||||
throw new InvalidOperationException("The client is connecting.");
|
throw new InvalidOperationException("The client is connecting.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Discord
|
|||||||
public static string Channel(Channel channel)
|
public static string Channel(Channel channel)
|
||||||
=> $"<#{channel.Id}>";
|
=> $"<#{channel.Id}>";
|
||||||
/// <summary> Returns the string used to create a mention to everyone in a channel. </summary>
|
/// <summary> Returns the string used to create a mention to everyone in a channel. </summary>
|
||||||
[Obsolete("Use Role.Mention instead")]
|
[Obsolete("Use Server.EveryoneRole.Mention instead")]
|
||||||
public static string Everyone()
|
public static string Everyone()
|
||||||
=> $"@everyone";
|
=> $"@everyone";
|
||||||
|
|
||||||
|
|||||||
148
src/Discord.Net/Helpers/TaskManager.cs
Normal file
148
src/Discord.Net/Helpers/TaskManager.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
/// <summary> Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. </summary>
|
||||||
|
public class TaskManager
|
||||||
|
{
|
||||||
|
private readonly object _lock;
|
||||||
|
private readonly Func<Task> _stopAction;
|
||||||
|
|
||||||
|
private CancellationTokenSource _cancelSource;
|
||||||
|
private Task _task;
|
||||||
|
|
||||||
|
public bool WasUnexpected => _wasUnexpected;
|
||||||
|
private bool _wasUnexpected;
|
||||||
|
|
||||||
|
public Exception Exception => _stopReason.SourceException;
|
||||||
|
private ExceptionDispatchInfo _stopReason;
|
||||||
|
|
||||||
|
public TaskManager()
|
||||||
|
{
|
||||||
|
_lock = new object();
|
||||||
|
}
|
||||||
|
public TaskManager(Action stopAction)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
_stopAction = TaskHelper.ToAsync(stopAction);
|
||||||
|
}
|
||||||
|
public TaskManager(Func<Task> stopAction)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
_stopAction = stopAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start(IEnumerable<Task> tasks, CancellationTokenSource cancelSource)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var task = _task;
|
||||||
|
if (task != null)
|
||||||
|
await Stop().ConfigureAwait(false);
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_cancelSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
if (_task != null)
|
||||||
|
continue; //Another thread sneaked in and started this manager before we got a lock, loop and try again
|
||||||
|
|
||||||
|
_stopReason = null;
|
||||||
|
_wasUnexpected = false;
|
||||||
|
|
||||||
|
Task[] tasksArray = tasks.ToArray();
|
||||||
|
Task<Task> anyTask = Task.WhenAny(tasksArray);
|
||||||
|
Task allTasks = Task.WhenAll(tasksArray);
|
||||||
|
|
||||||
|
_task = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
//Wait for the first task to stop or error
|
||||||
|
Task firstTask = await anyTask.ConfigureAwait(false);
|
||||||
|
|
||||||
|
//Signal the rest of the tasks to stop
|
||||||
|
if (firstTask.Exception != null)
|
||||||
|
SignalError(firstTask.Exception.GetBaseException(), true);
|
||||||
|
else
|
||||||
|
SignalStop();
|
||||||
|
|
||||||
|
//Wait for the other tasks;
|
||||||
|
await allTasks.ConfigureAwait(false);
|
||||||
|
|
||||||
|
//Run the cleanup function within our lock
|
||||||
|
await _stopAction().ConfigureAwait(false);
|
||||||
|
_task = null;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignalStop()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_task == null) return; //Are we running?
|
||||||
|
if (_cancelSource.IsCancellationRequested) return;
|
||||||
|
|
||||||
|
_cancelSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Task Stop()
|
||||||
|
{
|
||||||
|
Task task;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
//Cache the task so we still have something to await if Cleanup is run really quickly
|
||||||
|
task = _task;
|
||||||
|
if (task == null) return TaskHelper.CompletedTask; //Are we running?
|
||||||
|
if (_cancelSource.IsCancellationRequested) return task;
|
||||||
|
|
||||||
|
_cancelSource.Cancel();
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignalError(Exception ex, bool isUnexpected = true)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_task == null) return; //Are we running?
|
||||||
|
|
||||||
|
_cancelSource.Cancel();
|
||||||
|
_stopReason = ExceptionDispatchInfo.Capture(ex);
|
||||||
|
_wasUnexpected = isUnexpected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Task Error(Exception ex, bool isUnexpected = true)
|
||||||
|
{
|
||||||
|
Task task;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
//Cache the task so we still have something to await if Cleanup is run really quickly
|
||||||
|
task = _task;
|
||||||
|
if (task == null) return TaskHelper.CompletedTask; //Are we running?
|
||||||
|
if (_cancelSource.IsCancellationRequested) return task;
|
||||||
|
|
||||||
|
_cancelSource.Cancel();
|
||||||
|
_stopReason = ExceptionDispatchInfo.Capture(ex);
|
||||||
|
_wasUnexpected = isUnexpected;
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Throws an exception if one was captured. </summary>
|
||||||
|
public void Throw()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_stopReason != null)
|
||||||
|
_stopReason.Throw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,7 +77,7 @@ namespace Discord.Net.Rest
|
|||||||
.FirstOrDefault(x => x.Name.Equals("Retry-After", StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(x => x.Name.Equals("Retry-After", StringComparison.OrdinalIgnoreCase));
|
||||||
if (retryAfter != null)
|
if (retryAfter != null)
|
||||||
{
|
{
|
||||||
await Task.Delay((int)retryAfter.Value);
|
await Task.Delay((int)retryAfter.Value).ConfigureAwait(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new HttpException(response.StatusCode);
|
throw new HttpException(response.StatusCode);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Net.WebSockets
|
namespace Discord.Net.WebSockets
|
||||||
@@ -11,14 +12,11 @@ namespace Discord.Net.WebSockets
|
|||||||
public int LastSequence => _lastSeq;
|
public int LastSequence => _lastSeq;
|
||||||
private int _lastSeq;
|
private int _lastSeq;
|
||||||
|
|
||||||
public string Token => _token;
|
|
||||||
private string _token;
|
|
||||||
|
|
||||||
public string SessionId => _sessionId;
|
public string SessionId => _sessionId;
|
||||||
private string _sessionId;
|
private string _sessionId;
|
||||||
|
|
||||||
public GatewaySocket(DiscordConfig config, Logger logger)
|
public GatewaySocket(DiscordClient client, Logger logger)
|
||||||
: base(config, logger)
|
: base(client, logger)
|
||||||
{
|
{
|
||||||
Disconnected += async (s, e) =>
|
Disconnected += async (s, e) =>
|
||||||
{
|
{
|
||||||
@@ -27,18 +25,13 @@ namespace Discord.Net.WebSockets
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Connect(string token)
|
public async Task Connect()
|
||||||
{
|
{
|
||||||
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
|
||||||
|
|
||||||
_token = token;
|
|
||||||
await BeginConnect().ConfigureAwait(false);
|
await BeginConnect().ConfigureAwait(false);
|
||||||
SendIdentify(token);
|
SendIdentify();
|
||||||
}
|
}
|
||||||
private async Task Redirect(string server)
|
private async Task Redirect()
|
||||||
{
|
{
|
||||||
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await BeginConnect().ConfigureAwait(false);
|
await BeginConnect().ConfigureAwait(false);
|
||||||
SendResume();
|
SendResume();
|
||||||
}
|
}
|
||||||
@@ -47,12 +40,12 @@ namespace Discord.Net.WebSockets
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cancelToken = ParentCancelToken.Value;
|
var cancelToken = ParentCancelToken.Value;
|
||||||
await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false);
|
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
|
||||||
while (!cancelToken.IsCancellationRequested)
|
while (!cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Connect(_token).ConfigureAwait(false);
|
await Connect().ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { throw; }
|
catch (OperationCanceledException) { throw; }
|
||||||
@@ -60,21 +53,21 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
_logger.Log(LogSeverity.Error, $"Reconnect failed", ex);
|
_logger.Log(LogSeverity.Error, $"Reconnect failed", ex);
|
||||||
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
||||||
await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
|
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
}
|
}
|
||||||
public Task Disconnect()
|
public Task Disconnect() => TaskManager.Stop();
|
||||||
{
|
|
||||||
return SignalDisconnect(wait: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task Run()
|
protected override async Task Run()
|
||||||
{
|
{
|
||||||
await RunTasks();
|
List<Task> tasks = new List<Task>();
|
||||||
}
|
tasks.AddRange(_engine.GetTasks(_cancelToken));
|
||||||
|
tasks.Add(HeartbeatAsync(_cancelToken));
|
||||||
|
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task ProcessMessage(string json)
|
protected override async Task ProcessMessage(string json)
|
||||||
{
|
{
|
||||||
@@ -102,7 +95,7 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
RaiseReceivedDispatch(msg.Type, token);
|
RaiseReceivedDispatch(msg.Type, token);
|
||||||
if (msg.Type == "READY" || msg.Type == "RESUMED")
|
if (msg.Type == "READY" || msg.Type == "RESUMED")
|
||||||
await EndConnect(); //Complete the connect
|
EndConnect(); //Complete the connect
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GatewayOpCodes.Redirect:
|
case GatewayOpCodes.Redirect:
|
||||||
@@ -113,7 +106,7 @@ namespace Discord.Net.WebSockets
|
|||||||
Host = payload.Url;
|
Host = payload.Url;
|
||||||
if (_logger.Level >= LogSeverity.Info)
|
if (_logger.Level >= LogSeverity.Info)
|
||||||
_logger.Info("Redirected to " + payload.Url);
|
_logger.Info("Redirected to " + payload.Url);
|
||||||
await Redirect(payload.Url).ConfigureAwait(false);
|
await Redirect().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -124,12 +117,12 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendIdentify(string token)
|
public void SendIdentify()
|
||||||
{
|
{
|
||||||
var msg = new IdentifyCommand();
|
var msg = new IdentifyCommand();
|
||||||
msg.Payload.Token = token;
|
msg.Payload.Token = _client.Token;
|
||||||
msg.Payload.Properties["$device"] = "Discord.Net";
|
msg.Payload.Properties["$device"] = "Discord.Net";
|
||||||
if (_config.UseLargeThreshold)
|
if (_client.Config.UseLargeThreshold)
|
||||||
msg.Payload.LargeThreshold = 100;
|
msg.Payload.LargeThreshold = 100;
|
||||||
msg.Payload.Compress = true;
|
msg.Payload.Compress = true;
|
||||||
QueueMessage(msg);
|
QueueMessage(msg);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace Discord.Net.WebSockets
|
|||||||
_webSocket = new WS4NetWebSocket(host);
|
_webSocket = new WS4NetWebSocket(host);
|
||||||
_webSocket.EnableAutoSendPing = false;
|
_webSocket.EnableAutoSendPing = false;
|
||||||
_webSocket.NoDelay = true;
|
_webSocket.NoDelay = true;
|
||||||
_webSocket.Proxy = null; //Disable
|
_webSocket.Proxy = null;
|
||||||
|
|
||||||
_webSocket.DataReceived += OnWebSocketBinary;
|
_webSocket.DataReceived += OnWebSocketBinary;
|
||||||
_webSocket.MessageReceived += OnWebSocketText;
|
_webSocket.MessageReceived += OnWebSocketText;
|
||||||
@@ -81,15 +81,15 @@ namespace Discord.Net.WebSockets
|
|||||||
return TaskHelper.CompletedTask;
|
return TaskHelper.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnWebSocketError(object sender, ErrorEventArgs e)
|
private void OnWebSocketError(object sender, ErrorEventArgs e)
|
||||||
{
|
{
|
||||||
await _parent.SignalDisconnect(e.Exception, isUnexpected: true).ConfigureAwait(false);
|
_parent.TaskManager.SignalError(e.Exception);
|
||||||
_waitUntilConnect.Set();
|
_waitUntilConnect.Set();
|
||||||
}
|
}
|
||||||
private async void OnWebSocketClosed(object sender, EventArgs e)
|
private void OnWebSocketClosed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var ex = new Exception($"Connection lost or close message received.");
|
var ex = new Exception($"Connection lost or close message received.");
|
||||||
await _parent.SignalDisconnect(ex, isUnexpected: false/*true*/).ConfigureAwait(false);
|
_parent.TaskManager.SignalError(ex, isUnexpected: true);
|
||||||
_waitUntilConnect.Set();
|
_waitUntilConnect.Set();
|
||||||
}
|
}
|
||||||
private void OnWebSocketOpened(object sender, EventArgs e)
|
private void OnWebSocketOpened(object sender, EventArgs e)
|
||||||
|
|||||||
@@ -1,52 +1,40 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.ExceptionServices;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Net.WebSockets
|
namespace Discord.Net.WebSockets
|
||||||
{
|
{
|
||||||
public enum WebSocketState : byte
|
|
||||||
{
|
|
||||||
Disconnected,
|
|
||||||
Connecting,
|
|
||||||
Connected,
|
|
||||||
Disconnecting
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract partial class WebSocket
|
public abstract partial class WebSocket
|
||||||
{
|
{
|
||||||
protected readonly IWebSocketEngine _engine;
|
private readonly Semaphore _lock;
|
||||||
protected readonly DiscordConfig _config;
|
protected readonly IWebSocketEngine _engine;
|
||||||
|
protected readonly DiscordClient _client;
|
||||||
protected readonly ManualResetEventSlim _connectedEvent;
|
protected readonly ManualResetEventSlim _connectedEvent;
|
||||||
|
|
||||||
protected ExceptionDispatchInfo _disconnectReason;
|
|
||||||
protected bool _wasDisconnectUnexpected;
|
|
||||||
protected WebSocketState _disconnectState;
|
|
||||||
|
|
||||||
protected int _heartbeatInterval;
|
protected int _heartbeatInterval;
|
||||||
private DateTime _lastHeartbeat;
|
private DateTime _lastHeartbeat;
|
||||||
private Task _runTask;
|
|
||||||
|
|
||||||
public CancellationToken? ParentCancelToken { get; set; }
|
public CancellationToken? ParentCancelToken { get; set; }
|
||||||
public CancellationToken CancelToken => _cancelToken;
|
public CancellationToken CancelToken => _cancelToken;
|
||||||
private CancellationTokenSource _cancelTokenSource;
|
protected CancellationTokenSource _cancelTokenSource;
|
||||||
protected CancellationToken _cancelToken;
|
protected CancellationToken _cancelToken;
|
||||||
|
|
||||||
internal JsonSerializer Serializer => _serializer;
|
public JsonSerializer Serializer => _serializer;
|
||||||
protected JsonSerializer _serializer;
|
protected JsonSerializer _serializer;
|
||||||
|
|
||||||
public Logger Logger => _logger;
|
internal TaskManager TaskManager => _taskManager;
|
||||||
|
protected readonly TaskManager _taskManager;
|
||||||
|
|
||||||
|
public Logger Logger => _logger;
|
||||||
protected readonly Logger _logger;
|
protected readonly Logger _logger;
|
||||||
|
|
||||||
public string Host { get { return _host; } set { _host = value; } }
|
public string Host { get { return _host; } set { _host = value; } }
|
||||||
private string _host;
|
private string _host;
|
||||||
|
|
||||||
public WebSocketState State => (WebSocketState)_state;
|
public ConnectionState State => (ConnectionState)_state;
|
||||||
protected int _state;
|
protected int _state;
|
||||||
|
|
||||||
public event EventHandler Connected;
|
public event EventHandler Connected;
|
||||||
@@ -66,20 +54,22 @@ namespace Discord.Net.WebSockets
|
|||||||
Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error));
|
Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocket(DiscordConfig config, Logger logger)
|
public WebSocket(DiscordClient client, Logger logger)
|
||||||
{
|
{
|
||||||
_config = config;
|
_client = client;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_cancelToken = new CancellationToken(true);
|
_lock = new Semaphore(1, 1);
|
||||||
|
_taskManager = new TaskManager(Cleanup);
|
||||||
|
_cancelToken = new CancellationToken(true);
|
||||||
_connectedEvent = new ManualResetEventSlim(false);
|
_connectedEvent = new ManualResetEventSlim(false);
|
||||||
|
|
||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
_engine = new WS4NetEngine(this, _config, _logger);
|
_engine = new WS4NetEngine(this, client.Config, _logger);
|
||||||
#else
|
#else
|
||||||
//_engine = new BuiltInWebSocketEngine(this, _config, _logger);
|
//_engine = new BuiltInWebSocketEngine(this, client.Config, _logger);
|
||||||
#endif
|
#endif
|
||||||
_engine.BinaryMessage += (s, e) =>
|
_engine.BinaryMessage += (s, e) =>
|
||||||
{
|
{
|
||||||
using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2))
|
using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2))
|
||||||
using (var decompressed = new MemoryStream())
|
using (var decompressed = new MemoryStream())
|
||||||
@@ -91,10 +81,7 @@ namespace Discord.Net.WebSockets
|
|||||||
ProcessMessage(reader.ReadToEnd()).Wait();
|
ProcessMessage(reader.ReadToEnd()).Wait();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_engine.TextMessage += (s, e) =>
|
_engine.TextMessage += (s, e) => ProcessMessage(e.Message).Wait();
|
||||||
{
|
|
||||||
/*await*/ ProcessMessage(e.Message).Wait();
|
|
||||||
};
|
|
||||||
|
|
||||||
_serializer = new JsonSerializer();
|
_serializer = new JsonSerializer();
|
||||||
_serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
|
_serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
|
||||||
@@ -112,127 +99,59 @@ namespace Discord.Net.WebSockets
|
|||||||
|
|
||||||
protected async Task BeginConnect()
|
protected async Task BeginConnect()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_state = (int)WebSocketState.Connecting;
|
_lock.WaitOne();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _taskManager.Stop().ConfigureAwait(false);
|
||||||
|
_state = (int)ConnectionState.Connecting;
|
||||||
|
|
||||||
|
_cancelTokenSource = new CancellationTokenSource();
|
||||||
|
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token;
|
||||||
|
_lastHeartbeat = DateTime.UtcNow;
|
||||||
|
|
||||||
if (ParentCancelToken == null)
|
await _engine.Connect(Host, _cancelToken).ConfigureAwait(false);
|
||||||
throw new InvalidOperationException("Parent cancel token was never set.");
|
await Run().ConfigureAwait(false);
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
}
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token;
|
finally
|
||||||
|
{
|
||||||
if (_state != (int)WebSocketState.Connecting)
|
_lock.Release();
|
||||||
throw new InvalidOperationException("Socket is in the wrong state.");
|
}
|
||||||
|
|
||||||
_lastHeartbeat = DateTime.UtcNow;
|
|
||||||
await _engine.Connect(Host, _cancelToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
_runTask = Run();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await SignalDisconnect(ex, isUnexpected: false).ConfigureAwait(false);
|
_taskManager.SignalError(ex, true);
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected async Task EndConnect()
|
protected void EndConnect()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_state = (int)WebSocketState.Connected;
|
_state = (int)ConnectionState.Connected;
|
||||||
|
|
||||||
_connectedEvent.Set();
|
_connectedEvent.Set();
|
||||||
RaiseConnected();
|
RaiseConnected();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
|
||||||
await SignalDisconnect(ex, isUnexpected: false).ConfigureAwait(false);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected internal async Task SignalDisconnect(Exception ex = null, bool isUnexpected = false, bool wait = false)
|
|
||||||
{
|
|
||||||
//If in either connecting or connected state, get a lock by being the first to switch to disconnecting
|
|
||||||
int oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connecting);
|
|
||||||
if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected
|
|
||||||
bool hasWriterLock = oldState == (int)WebSocketState.Connecting; //Caused state change
|
|
||||||
if (!hasWriterLock)
|
|
||||||
{
|
|
||||||
oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connected);
|
|
||||||
if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected
|
|
||||||
hasWriterLock = oldState == (int)WebSocketState.Connected; //Caused state change
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasWriterLock)
|
|
||||||
{
|
{
|
||||||
if (ex != null)
|
_taskManager.SignalError(ex, true);
|
||||||
_logger.Log(LogSeverity.Error, "Error", ex);
|
}
|
||||||
CaptureError(ex ?? new Exception("Disconnect was requested."), isUnexpected);
|
|
||||||
_cancelTokenSource.Cancel();
|
|
||||||
if (_disconnectState == WebSocketState.Connecting) //_runTask was never made
|
|
||||||
await Cleanup().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wait)
|
|
||||||
{
|
|
||||||
Task task = _runTask;
|
|
||||||
if (_runTask != null)
|
|
||||||
await task.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void CaptureError(Exception ex, bool isUnexpected)
|
|
||||||
{
|
|
||||||
_disconnectReason = ExceptionDispatchInfo.Capture(ex);
|
|
||||||
_wasDisconnectUnexpected = isUnexpected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task Run();
|
protected abstract Task Run();
|
||||||
protected async Task RunTasks(params Task[] tasks)
|
|
||||||
{
|
|
||||||
//Get all async tasks
|
|
||||||
tasks = tasks
|
|
||||||
.Concat(_engine.GetTasks(_cancelToken))
|
|
||||||
.Concat(new Task[] { HeartbeatAsync(_cancelToken) })
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
//Create group tasks
|
|
||||||
Task firstTask = Task.WhenAny(tasks);
|
|
||||||
Task allTasks = Task.WhenAll(tasks);
|
|
||||||
|
|
||||||
//Wait until the first task ends/errors and capture the error
|
|
||||||
Exception ex = null;
|
|
||||||
try { await firstTask.ConfigureAwait(false); }
|
|
||||||
catch (Exception ex2) { ex = ex2; }
|
|
||||||
|
|
||||||
//Ensure all other tasks are signaled to end.
|
|
||||||
await SignalDisconnect(ex, ex != null, true).ConfigureAwait(false);
|
|
||||||
|
|
||||||
//Wait for the remaining tasks to complete
|
|
||||||
try { await allTasks.ConfigureAwait(false); }
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
//Start cleanup
|
|
||||||
await Cleanup().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual async Task Cleanup()
|
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().ConfigureAwait(false);
|
await _engine.Disconnect().ConfigureAwait(false);
|
||||||
_cancelTokenSource = null;
|
_cancelTokenSource = null;
|
||||||
var oldState = _state;
|
var oldState = _state;
|
||||||
_state = (int)WebSocketState.Disconnected;
|
|
||||||
_runTask = null;
|
|
||||||
_connectedEvent.Reset();
|
_connectedEvent.Reset();
|
||||||
|
|
||||||
if (disconnectState == WebSocketState.Connected)
|
if (oldState == (int)ConnectionState.Connected)
|
||||||
RaiseDisconnected(wasDisconnectUnexpected, _disconnectReason?.SourceException);
|
{
|
||||||
|
_state = (int)ConnectionState.Disconnected;
|
||||||
|
RaiseDisconnected(_taskManager.WasUnexpected, _taskManager.Exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task ProcessMessage(string json)
|
protected virtual Task ProcessMessage(string json)
|
||||||
@@ -240,8 +159,7 @@ namespace Discord.Net.WebSockets
|
|||||||
if (_logger.Level >= LogSeverity.Debug)
|
if (_logger.Level >= LogSeverity.Debug)
|
||||||
_logger.Debug( $"In: {json}");
|
_logger.Debug( $"In: {json}");
|
||||||
return TaskHelper.CompletedTask;
|
return TaskHelper.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void QueueMessage(object message)
|
protected void QueueMessage(object message)
|
||||||
{
|
{
|
||||||
string json = JsonConvert.SerializeObject(message);
|
string json = JsonConvert.SerializeObject(message);
|
||||||
@@ -250,7 +168,7 @@ namespace Discord.Net.WebSockets
|
|||||||
_engine.QueueMessage(json);
|
_engine.QueueMessage(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HeartbeatAsync(CancellationToken cancelToken)
|
protected Task HeartbeatAsync(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
return Task.Run(async () =>
|
return Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -258,7 +176,7 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
while (!cancelToken.IsCancellationRequested)
|
while (!cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
if (_state == (int)WebSocketState.Connected)
|
if (_state == (int)ConnectionState.Connected)
|
||||||
{
|
{
|
||||||
SendHeartbeat();
|
SendHeartbeat();
|
||||||
await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false);
|
await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false);
|
||||||
@@ -269,18 +187,12 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public abstract void SendHeartbeat();
|
||||||
|
|
||||||
protected internal void ThrowError()
|
protected internal void ThrowError()
|
||||||
{
|
{
|
||||||
if (_wasDisconnectUnexpected)
|
_taskManager.Throw();
|
||||||
{
|
|
||||||
var reason = _disconnectReason;
|
|
||||||
_disconnectReason = null;
|
|
||||||
reason.Throw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void SendHeartbeat();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,9 +112,9 @@ namespace Discord.Tests
|
|||||||
public static void Cleanup()
|
public static void Cleanup()
|
||||||
{
|
{
|
||||||
WaitMany(
|
WaitMany(
|
||||||
_hostClient.State == DiscordClientState.Connected ? _hostClient.AllServers.Select(x => _hostClient.LeaveServer(x)) : null,
|
_hostClient.State == ConnectionState.Connected ? _hostClient.AllServers.Select(x => _hostClient.LeaveServer(x)) : null,
|
||||||
_targetBot.State == DiscordClientState.Connected ? _targetBot.AllServers.Select(x => _targetBot.LeaveServer(x)) : null,
|
_targetBot.State == ConnectionState.Connected ? _targetBot.AllServers.Select(x => _targetBot.LeaveServer(x)) : null,
|
||||||
_observerBot.State == DiscordClientState.Connected ? _observerBot.AllServers.Select(x => _observerBot.LeaveServer(x)) : null);
|
_observerBot.State == ConnectionState.Connected ? _observerBot.AllServers.Select(x => _observerBot.LeaveServer(x)) : null);
|
||||||
|
|
||||||
WaitAll(
|
WaitAll(
|
||||||
_hostClient.Disconnect(),
|
_hostClient.Disconnect(),
|
||||||
|
|||||||
Reference in New Issue
Block a user