Browse Source

Added VOICE_SERVER_UPDATE handling

tags/1.0-rc
RogueException 9 years ago
parent
commit
a529b6bd07
8 changed files with 159 additions and 121 deletions
  1. +21
    -13
      src/Discord.Net/API/DiscordVoiceAPIClient.cs
  2. +42
    -82
      src/Discord.Net/Audio/AudioClient.cs
  3. +1
    -1
      src/Discord.Net/Audio/IAudioClient.cs
  4. +9
    -7
      src/Discord.Net/DiscordClient.cs
  5. +2
    -2
      src/Discord.Net/DiscordSocketClient.Events.cs
  6. +27
    -14
      src/Discord.Net/DiscordSocketClient.cs
  7. +55
    -2
      src/Discord.Net/Entities/WebSocket/CachedGuild.cs
  8. +2
    -0
      src/Discord.Net/IDiscordClient.cs

+ 21
- 13
src/Discord.Net/API/DiscordVoiceAPIClient.cs View File

@@ -28,24 +28,19 @@ namespace Discord.Audio
private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>(); private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>();
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();

private readonly ulong _userId;
private readonly string _token;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;
private readonly IWebSocketClient _gatewayClient; private readonly IWebSocketClient _gatewayClient;
private readonly SemaphoreSlim _connectionLock; private readonly SemaphoreSlim _connectionLock;
private CancellationTokenSource _connectCancelToken; private CancellationTokenSource _connectCancelToken;
private bool _isDisposed;


public ulong GuildId { get; }
public string SessionId { get; }
public ulong GuildId { get; }
public ConnectionState ConnectionState { get; private set; } public ConnectionState ConnectionState { get; private set; }


internal DiscordVoiceAPIClient(ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, JsonSerializer serializer = null)
internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, JsonSerializer serializer = null)
{ {
GuildId = guildId; GuildId = guildId;
_userId = userId;
SessionId = sessionId;
_token = token;
_connectionLock = new SemaphoreSlim(1, 1); _connectionLock = new SemaphoreSlim(1, 1);


_gatewayClient = webSocketProvider(); _gatewayClient = webSocketProvider();
@@ -78,6 +73,19 @@ namespace Discord.Audio


_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
} }
void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
_connectCancelToken?.Dispose();
(_gatewayClient as IDisposable)?.Dispose();
}
_isDisposed = true;
}
}
public void Dispose() => Dispose(true);


public Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) public Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null)
{ {
@@ -105,16 +113,16 @@ namespace Discord.Audio
}); });
} }


public async Task ConnectAsync(string url)
public async Task ConnectAsync(string url, ulong userId, string sessionId, string token)
{ {
await _connectionLock.WaitAsync().ConfigureAwait(false); await _connectionLock.WaitAsync().ConfigureAwait(false);
try try
{ {
await ConnectInternalAsync(url).ConfigureAwait(false);
await ConnectInternalAsync(url, userId, sessionId, token).ConfigureAwait(false);
} }
finally { _connectionLock.Release(); } finally { _connectionLock.Release(); }
} }
private async Task ConnectInternalAsync(string url)
private async Task ConnectInternalAsync(string url, ulong userId, string sessionId, string token)
{ {
ConnectionState = ConnectionState.Connecting; ConnectionState = ConnectionState.Connecting;
try try
@@ -123,7 +131,7 @@ namespace Discord.Audio
_gatewayClient.SetCancelToken(_connectCancelToken.Token); _gatewayClient.SetCancelToken(_connectCancelToken.Token);
await _gatewayClient.ConnectAsync(url).ConfigureAwait(false); await _gatewayClient.ConnectAsync(url).ConfigureAwait(false);


await SendIdentityAsync(GuildId, _userId, SessionId, _token).ConfigureAwait(false);
await SendIdentityAsync(GuildId, userId, sessionId, token).ConfigureAwait(false);


ConnectionState = ConnectionState.Connected; ConnectionState = ConnectionState.Connected;
} }


+ 42
- 82
src/Discord.Net/Audio/AudioClient.cs View File

@@ -1,7 +1,6 @@
using Discord.API.Voice; using Discord.API.Voice;
using Discord.Logging; using Discord.Logging;
using Discord.Net.Converters; using Discord.Net.Converters;
using Discord.Net.WebSockets;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Threading; using System.Threading;
@@ -9,7 +8,7 @@ using System.Threading.Tasks;


namespace Discord.Audio namespace Discord.Audio
{ {
internal class AudioClient : IAudioClient
internal class AudioClient : IAudioClient, IDisposable
{ {
public event Func<Task> Connected public event Func<Task> Connected
{ {
@@ -17,12 +16,12 @@ namespace Discord.Audio
remove { _connectedEvent.Remove(value); } remove { _connectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
public event Func<Task> Disconnected
public event Func<Exception, Task> Disconnected
{ {
add { _disconnectedEvent.Add(value); } add { _disconnectedEvent.Add(value); }
remove { _disconnectedEvent.Remove(value); } remove { _disconnectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Task>> _disconnectedEvent = new AsyncEvent<Func<Task>>();
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
public event Func<int, int, Task> LatencyUpdated public event Func<int, int, Task> LatencyUpdated
{ {
add { _latencyUpdatedEvent.Add(value); } add { _latencyUpdatedEvent.Add(value); }
@@ -34,28 +33,30 @@ namespace Discord.Audio
#if BENCHMARK #if BENCHMARK
private readonly ILogger _benchmarkLogger; private readonly ILogger _benchmarkLogger;
#endif #endif
private readonly JsonSerializer _serializer;
internal readonly SemaphoreSlim _connectionLock; internal readonly SemaphoreSlim _connectionLock;
private readonly JsonSerializer _serializer;


private TaskCompletionSource<bool> _connectTask; private TaskCompletionSource<bool> _connectTask;
private CancellationTokenSource _cancelToken; private CancellationTokenSource _cancelToken;
private Task _heartbeatTask, _reconnectTask;
private Task _heartbeatTask;
private long _heartbeatTime; private long _heartbeatTime;
private bool _isReconnecting;
private string _url; private string _url;
private bool _isDisposed;


private DiscordSocketClient Discord { get; }
public CachedGuild Guild { get; }
public DiscordVoiceAPIClient ApiClient { get; private set; } public DiscordVoiceAPIClient ApiClient { get; private set; }
public ConnectionState ConnectionState { get; private set; } public ConnectionState ConnectionState { get; private set; }
public int Latency { get; private set; } public int Latency { get; private set; }


private DiscordSocketClient Discord => Guild.Discord;

/// <summary> Creates a new REST/WebSocket discord client. </summary> /// <summary> Creates a new REST/WebSocket discord client. </summary>
internal AudioClient(DiscordSocketClient discord, ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, ILogManager logManager)
internal AudioClient(CachedGuild guild)
{ {
Discord = discord;
Guild = guild;


_webSocketLogger = logManager.CreateLogger("Audio");
_udpLogger = logManager.CreateLogger("AudioUDP");
_webSocketLogger = Discord.LogManager.CreateLogger("Audio");
_udpLogger = Discord.LogManager.CreateLogger("AudioUDP");
#if BENCHMARK #if BENCHMARK
_benchmarkLogger = logManager.CreateLogger("Benchmark"); _benchmarkLogger = logManager.CreateLogger("Benchmark");
#endif #endif
@@ -69,38 +70,34 @@ namespace Discord.Audio
e.ErrorContext.Handled = true; e.ErrorContext.Handled = true;
}; };
ApiClient = new DiscordVoiceAPIClient(guildId, userId, sessionId, token, webSocketProvider);
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider);


ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false); ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false);
ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedEvent += ProcessMessageAsync;
ApiClient.Disconnected += async ex => ApiClient.Disconnected += async ex =>
{ {
if (ex != null) if (ex != null)
{
await _webSocketLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false); await _webSocketLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
await StartReconnectAsync().ConfigureAwait(false);
}
else else
await _webSocketLogger.WarningAsync($"Connection Closed").ConfigureAwait(false); await _webSocketLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
}; };
} }


/// <inheritdoc /> /// <inheritdoc />
public async Task ConnectAsync(string url)
public async Task ConnectAsync(string url, ulong userId, string sessionId, string token)
{ {
await _connectionLock.WaitAsync().ConfigureAwait(false); await _connectionLock.WaitAsync().ConfigureAwait(false);
try try
{ {
_isReconnecting = false;
await ConnectInternalAsync(url).ConfigureAwait(false);
await ConnectInternalAsync(url, userId, sessionId, token).ConfigureAwait(false);
} }
finally { _connectionLock.Release(); } finally { _connectionLock.Release(); }
} }
private async Task ConnectInternalAsync(string url)
private async Task ConnectInternalAsync(string url, ulong userId, string sessionId, string token)
{ {
var state = ConnectionState; var state = ConnectionState;
if (state == ConnectionState.Connecting || state == ConnectionState.Connected) if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);


ConnectionState = ConnectionState.Connecting; ConnectionState = ConnectionState.Connecting;
await _webSocketLogger.InfoAsync("Connecting").ConfigureAwait(false); await _webSocketLogger.InfoAsync("Connecting").ConfigureAwait(false);
@@ -109,7 +106,7 @@ namespace Discord.Audio
_url = url; _url = url;
_connectTask = new TaskCompletionSource<bool>(); _connectTask = new TaskCompletionSource<bool>();
_cancelToken = new CancellationTokenSource(); _cancelToken = new CancellationTokenSource();
await ApiClient.ConnectAsync(url).ConfigureAwait(false);
await ApiClient.ConnectAsync(url, userId, sessionId, token).ConfigureAwait(false);
await _connectedEvent.InvokeAsync().ConfigureAwait(false); await _connectedEvent.InvokeAsync().ConfigureAwait(false);


await _connectTask.Task.ConfigureAwait(false); await _connectTask.Task.ConfigureAwait(false);
@@ -119,7 +116,7 @@ namespace Discord.Audio
} }
catch (Exception) catch (Exception)
{ {
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);
throw; throw;
} }
} }
@@ -129,12 +126,20 @@ namespace Discord.Audio
await _connectionLock.WaitAsync().ConfigureAwait(false); await _connectionLock.WaitAsync().ConfigureAwait(false);
try try
{ {
_isReconnecting = false;
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
}
private async Task DisconnectAsync(Exception ex)
{
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await DisconnectInternalAsync(ex).ConfigureAwait(false);
} }
finally { _connectionLock.Release(); } finally { _connectionLock.Release(); }
} }
private async Task DisconnectInternalAsync()
private async Task DisconnectInternalAsync(Exception ex)
{ {
if (ConnectionState == ConnectionState.Disconnected) return; if (ConnectionState == ConnectionState.Disconnected) return;
ConnectionState = ConnectionState.Disconnecting; ConnectionState = ConnectionState.Disconnecting;
@@ -155,61 +160,7 @@ namespace Discord.Audio
ConnectionState = ConnectionState.Disconnected; ConnectionState = ConnectionState.Disconnected;
await _webSocketLogger.InfoAsync("Disconnected").ConfigureAwait(false); await _webSocketLogger.InfoAsync("Disconnected").ConfigureAwait(false);


await _disconnectedEvent.InvokeAsync().ConfigureAwait(false);
}

private async Task StartReconnectAsync()
{
//TODO: Is this thread-safe?
if (_reconnectTask != null) return;

await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
if (_reconnectTask != null) return;
_isReconnecting = true;
_reconnectTask = ReconnectInternalAsync();
}
finally { _connectionLock.Release(); }
}
private async Task ReconnectInternalAsync()
{
try
{
int nextReconnectDelay = 1000;
while (_isReconnecting)
{
try
{
await Task.Delay(nextReconnectDelay).ConfigureAwait(false);
nextReconnectDelay *= 2;
if (nextReconnectDelay > 30000)
nextReconnectDelay = 30000;

await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
await ConnectInternalAsync(_url).ConfigureAwait(false);
}
finally { _connectionLock.Release(); }
return;
}
catch (Exception ex)
{
await _webSocketLogger.WarningAsync("Reconnect failed", ex).ConfigureAwait(false);
}
}
}
finally
{
await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
_isReconnecting = false;
_reconnectTask = null;
}
finally { _connectionLock.Release(); }
}
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
} }


private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
@@ -285,7 +236,7 @@ namespace Discord.Audio
if (ConnectionState == ConnectionState.Connected) if (ConnectionState == ConnectionState.Connected)
{ {
await _webSocketLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false); await _webSocketLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false);
await StartReconnectAsync().ConfigureAwait(false);
await DisconnectInternalAsync(new Exception("Server missed last heartbeat")).ConfigureAwait(false);
return; return;
} }
} }
@@ -296,5 +247,14 @@ namespace Discord.Audio
} }
catch (OperationCanceledException) { } catch (OperationCanceledException) { }
} }

internal virtual void Dispose(bool disposing)
{
if (!_isDisposed)
_isDisposed = true;
ApiClient.Dispose();
}
/// <inheritdoc />
public void Dispose() => Dispose(true);
} }
} }

+ 1
- 1
src/Discord.Net/Audio/IAudioClient.cs View File

@@ -6,7 +6,7 @@ namespace Discord.Audio
public interface IAudioClient public interface IAudioClient
{ {
event Func<Task> Connected; event Func<Task> Connected;
event Func<Task> Disconnected;
event Func<Exception, Task> Disconnected;
event Func<int, int, Task> LatencyUpdated; event Func<int, int, Task> LatencyUpdated;


DiscordVoiceAPIClient ApiClient { get; } DiscordVoiceAPIClient ApiClient { get; }


+ 9
- 7
src/Discord.Net/DiscordClient.cs View File

@@ -26,13 +26,13 @@ namespace Discord


internal readonly ILogger _discordLogger, _restLogger, _queueLogger; internal readonly ILogger _discordLogger, _restLogger, _queueLogger;
internal readonly SemaphoreSlim _connectionLock; internal readonly SemaphoreSlim _connectionLock;
internal readonly LogManager _log;
internal readonly RequestQueue _requestQueue; internal readonly RequestQueue _requestQueue;
internal bool _isDisposed; internal bool _isDisposed;
internal SelfUser _currentUser; internal SelfUser _currentUser;


public API.DiscordApiClient ApiClient { get; }
internal LogManager LogManager { get; }
public LoginState LoginState { get; private set; } public LoginState LoginState { get; private set; }
public API.DiscordApiClient ApiClient { get; private set; }


/// <summary> Creates a new REST-only discord client. </summary> /// <summary> Creates a new REST-only discord client. </summary>
public DiscordClient() public DiscordClient()
@@ -40,11 +40,11 @@ namespace Discord
/// <summary> Creates a new REST-only discord client. </summary> /// <summary> Creates a new REST-only discord client. </summary>
public DiscordClient(DiscordConfig config) public DiscordClient(DiscordConfig config)
{ {
_log = new LogManager(config.LogLevel);
_log.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
_discordLogger = _log.CreateLogger("Discord");
_restLogger = _log.CreateLogger("Rest");
_queueLogger = _log.CreateLogger("Queue");
LogManager = new LogManager(config.LogLevel);
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
_discordLogger = LogManager.CreateLogger("Discord");
_restLogger = LogManager.CreateLogger("Rest");
_queueLogger = LogManager.CreateLogger("Queue");


_connectionLock = new SemaphoreSlim(1, 1); _connectionLock = new SemaphoreSlim(1, 1);


@@ -267,6 +267,8 @@ namespace Discord
public void Dispose() => Dispose(true); public void Dispose() => Dispose(true);
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
ILogManager IDiscordClient.LogManager => LogManager;

Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); } Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); }
Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); } Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); }
} }


+ 2
- 2
src/Discord.Net/DiscordSocketClient.Events.cs View File

@@ -13,12 +13,12 @@ namespace Discord
remove { _connectedEvent.Remove(value); } remove { _connectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
public event Func<Task> Disconnected
public event Func<Exception, Task> Disconnected
{ {
add { _disconnectedEvent.Add(value); } add { _disconnectedEvent.Add(value); }
remove { _disconnectedEvent.Remove(value); } remove { _disconnectedEvent.Remove(value); }
} }
private readonly AsyncEvent<Func<Task>> _disconnectedEvent = new AsyncEvent<Func<Task>>();
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
public event Func<Task> Ready public event Func<Task> Ready
{ {
add { _readyEvent.Add(value); } add { _readyEvent.Add(value); }


+ 27
- 14
src/Discord.Net/DiscordSocketClient.cs View File

@@ -75,7 +75,7 @@ namespace Discord
AudioMode = config.AudioMode; AudioMode = config.AudioMode;
WebSocketProvider = config.WebSocketProvider; WebSocketProvider = config.WebSocketProvider;


_gatewayLogger = _log.CreateLogger("Gateway");
_gatewayLogger = LogManager.CreateLogger("Gateway");
#if BENCHMARK #if BENCHMARK
_benchmarkLogger = _log.CreateLogger("Benchmark"); _benchmarkLogger = _log.CreateLogger("Benchmark");
#endif #endif
@@ -94,7 +94,7 @@ namespace Discord
if (ex != null) if (ex != null)
{ {
await _gatewayLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false); await _gatewayLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
await StartReconnectAsync().ConfigureAwait(false);
await StartReconnectAsync(ex).ConfigureAwait(false);
} }
else else
await _gatewayLogger.WarningAsync($"Connection Closed").ConfigureAwait(false); await _gatewayLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
@@ -112,7 +112,7 @@ namespace Discord
protected override async Task OnLogoutAsync() protected override async Task OnLogoutAsync()
{ {
if (ConnectionState != ConnectionState.Disconnected) if (ConnectionState != ConnectionState.Disconnected)
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);


_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>(); _voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
} }
@@ -142,7 +142,7 @@ namespace Discord


var state = ConnectionState; var state = ConnectionState;
if (state == ConnectionState.Connecting || state == ConnectionState.Connected) if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);


ConnectionState = ConnectionState.Connecting; ConnectionState = ConnectionState.Connecting;
await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false); await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false);
@@ -165,7 +165,7 @@ namespace Discord
} }
catch (Exception) catch (Exception)
{ {
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);
throw; throw;
} }
} }
@@ -176,11 +176,11 @@ namespace Discord
try try
{ {
_isReconnecting = false; _isReconnecting = false;
await DisconnectInternalAsync().ConfigureAwait(false);
await DisconnectInternalAsync(null).ConfigureAwait(false);
} }
finally { _connectionLock.Release(); } finally { _connectionLock.Release(); }
} }
private async Task DisconnectInternalAsync()
private async Task DisconnectInternalAsync(Exception ex)
{ {
ulong guildId; ulong guildId;


@@ -211,10 +211,10 @@ namespace Discord
ConnectionState = ConnectionState.Disconnected; ConnectionState = ConnectionState.Disconnected;
await _gatewayLogger.InfoAsync("Disconnected").ConfigureAwait(false); await _gatewayLogger.InfoAsync("Disconnected").ConfigureAwait(false);


await _disconnectedEvent.InvokeAsync().ConfigureAwait(false);
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
} }


private async Task StartReconnectAsync()
private async Task StartReconnectAsync(Exception ex)
{ {
//TODO: Is this thread-safe? //TODO: Is this thread-safe?
if (_reconnectTask != null) return; if (_reconnectTask != null) return;
@@ -222,6 +222,7 @@ namespace Discord
await _connectionLock.WaitAsync().ConfigureAwait(false); await _connectionLock.WaitAsync().ConfigureAwait(false);
try try
{ {
await DisconnectInternalAsync(ex).ConfigureAwait(false);
if (_reconnectTask != null) return; if (_reconnectTask != null) return;
_isReconnecting = true; _isReconnecting = true;
_reconnectTask = ReconnectInternalAsync(); _reconnectTask = ReconnectInternalAsync();
@@ -469,7 +470,7 @@ namespace Discord
await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false);
await _gatewayLogger.WarningAsync("Server requested a reconnect").ConfigureAwait(false); await _gatewayLogger.WarningAsync("Server requested a reconnect").ConfigureAwait(false);


await StartReconnectAsync().ConfigureAwait(false);
await StartReconnectAsync(new Exception("Server requested a reconnect")).ConfigureAwait(false);
} }
break; break;
case GatewayOpCode.Dispatch: case GatewayOpCode.Dispatch:
@@ -1113,9 +1114,7 @@ namespace Discord


var user = guild.GetUser(data.UserId); var user = guild.GetUser(data.UserId);
if (user != null) if (user != null)
{
await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false); await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false);
}
else else
{ {
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false);
@@ -1131,7 +1130,21 @@ namespace Discord
} }
break; break;
case "VOICE_SERVER_UPDATE": case "VOICE_SERVER_UPDATE":
await _gatewayLogger.DebugAsync("Ignored Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false);
await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false);

if (AudioMode != AudioMode.Disabled)
{
var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
await guild.ConnectAudio("wss://" + data.Endpoint, data.Token).ConfigureAwait(false);
else
{
await _gatewayLogger.WarningAsync("VOICE_SERVER_UPDATE referenced an unknown guild.").ConfigureAwait(false);
return;
}
}

return; return;


//Ignored (User only) //Ignored (User only)
@@ -1183,7 +1196,7 @@ namespace Discord
if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? false)) if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? false))
{ {
await _gatewayLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false); await _gatewayLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false);
await StartReconnectAsync().ConfigureAwait(false);
await StartReconnectAsync(new Exception("Server missed last heartbeat")).ConfigureAwait(false);
return; return;
} }
} }


+ 55
- 2
src/Discord.Net/Entities/WebSocket/CachedGuild.cs View File

@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ChannelModel = Discord.API.Channel; using ChannelModel = Discord.API.Channel;
using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent; using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent;
@@ -17,8 +18,9 @@ using VoiceStateModel = Discord.API.VoiceState;


namespace Discord namespace Discord
{ {
internal class CachedGuild : Guild, IUserGuild, ICachedEntity<ulong>
internal class CachedGuild : Guild, ICachedEntity<ulong>, IGuild, IUserGuild
{ {
private readonly SemaphoreSlim _audioLock;
private TaskCompletionSource<bool> _downloaderPromise; private TaskCompletionSource<bool> _downloaderPromise;
private ConcurrentHashSet<ulong> _channels; private ConcurrentHashSet<ulong> _channels;
private ConcurrentDictionary<ulong, CachedGuildUser> _members; private ConcurrentDictionary<ulong, CachedGuildUser> _members;
@@ -27,7 +29,7 @@ namespace Discord
public bool Available { get; private set; } public bool Available { get; private set; }
public int MemberCount { get; private set; } public int MemberCount { get; private set; }
public int DownloadedMemberCount { get; private set; } public int DownloadedMemberCount { get; private set; }
public IAudioClient AudioClient { get; private set; }
public AudioClient AudioClient { get; private set; }


public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
public Task DownloaderPromise => _downloaderPromise.Task; public Task DownloaderPromise => _downloaderPromise.Task;
@@ -48,6 +50,7 @@ namespace Discord
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
{ {
_audioLock = new SemaphoreSlim(1, 1);
_downloaderPromise = new TaskCompletionSource<bool>(); _downloaderPromise = new TaskCompletionSource<bool>();
Update(model, UpdateSource.Creation, dataStore); Update(model, UpdateSource.Creation, dataStore);
} }
@@ -236,6 +239,55 @@ namespace Discord
return null; return null;
} }


public async Task ConnectAudio(string url, string token)
{
AudioClient audioClient;
await _audioLock.WaitAsync().ConfigureAwait(false);
var voiceState = GetVoiceState(CurrentUser.Id).Value;
try
{
audioClient = AudioClient;
if (audioClient == null)
{
audioClient = new AudioClient(this);
audioClient.Disconnected += async ex =>
{
await _audioLock.WaitAsync().ConfigureAwait(false);
try
{
if (ex != null)
{
//Reconnect if we still have channel info.
//TODO: Is this threadsafe? Could channel data be deleted before we access it?
var voiceState2 = GetVoiceState(CurrentUser.Id);
if (voiceState2.HasValue)
{
var voiceChannelId = voiceState2.Value.VoiceChannel?.Id;
if (voiceChannelId != null)
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted);
}
}
else
{
try { AudioClient.Dispose(); } catch { }
AudioClient = null;
}
}
finally
{
_audioLock.Release();
}
};
AudioClient = audioClient;
}
}
finally
{
_audioLock.Release();
}
await audioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
}

public CachedGuild Clone() => MemberwiseClone() as CachedGuild; public CachedGuild Clone() => MemberwiseClone() as CachedGuild;


new internal ICachedGuildChannel ToChannel(ChannelModel model) new internal ICachedGuildChannel ToChannel(ChannelModel model)
@@ -253,5 +305,6 @@ namespace Discord


bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id; bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id;
GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions; GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions;
IAudioClient IGuild.AudioClient => AudioClient;
} }
} }

+ 2
- 0
src/Discord.Net/IDiscordClient.cs View File

@@ -1,4 +1,5 @@
using Discord.API; using Discord.API;
using Discord.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -13,6 +14,7 @@ namespace Discord
ConnectionState ConnectionState { get; } ConnectionState ConnectionState { get; }


DiscordApiClient ApiClient { get; } DiscordApiClient ApiClient { get; }
ILogManager LogManager { get; }
Task LoginAsync(TokenType tokenType, string token, bool validateToken = true); Task LoginAsync(TokenType tokenType, string token, bool validateToken = true);
Task LogoutAsync(); Task LogoutAsync();


Loading…
Cancel
Save