Browse Source

Abstracted serialization

voice-allocs
RogueException 7 years ago
parent
commit
c83b1bd321
24 changed files with 290 additions and 202 deletions
  1. +2
    -2
      src/Discord.Net.Core/Format.cs
  2. +6
    -4
      src/Discord.Net.Rest/BaseDiscordClient.cs
  3. +9
    -13
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  4. +13
    -4
      src/Discord.Net.Rest/DiscordRestClient.cs
  5. +1
    -4
      src/Discord.Net.Rest/Net/DefaultRestClient.cs
  6. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs
  7. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs
  8. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs
  9. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs
  10. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs
  11. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs
  12. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs
  13. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs
  14. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs
  15. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs
  16. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs
  17. +80
    -0
      src/Discord.Net.Rest/Serialization/Serializer.cs
  18. +40
    -30
      src/Discord.Net.Rpc/DiscordRpcApiClient.cs
  19. +30
    -29
      src/Discord.Net.Rpc/DiscordRpcClient.cs
  20. +12
    -14
      src/Discord.Net.WebSocket/Audio/AudioClient.cs
  21. +15
    -7
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  22. +9
    -12
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  23. +48
    -52
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  24. +14
    -20
      src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs

+ 2
- 2
src/Discord.Net.Core/Format.cs View File

@@ -3,7 +3,7 @@
public static class Format
{
// Characters which need escaping
private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" };
private static string[] _sensitiveCharacters = { "\\", "*", "_", "~", "`" };

/// <summary> Returns a markdown-formatted string with bold formatting. </summary>
public static string Bold(string text) => $"**{text}**";
@@ -26,7 +26,7 @@
/// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary>
public static string Sanitize(string text)
{
foreach (string unsafeChar in SensitiveCharacters)
foreach (string unsafeChar in _sensitiveCharacters)
text = text.Replace(unsafeChar, $"\\{unsafeChar}");
return text;
}


+ 6
- 4
src/Discord.Net.Rest/BaseDiscordClient.cs View File

@@ -22,23 +22,25 @@ namespace Discord.Rest
private readonly SemaphoreSlim _stateLock;
private bool _isFirstLogin, _isDisposed;

internal API.DiscordRestApiClient ApiClient { get; }
internal LogManager LogManager { get; }
internal API.DiscordRestApiClient ApiClient { get; private set; }
public LoginState LoginState { get; private set; }
public ISelfUser CurrentUser { get; protected set; }
public TokenType TokenType => ApiClient.AuthTokenType;
/// <summary> Creates a new REST-only discord client. </summary>
internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client)
internal BaseDiscordClient(DiscordRestConfig config)
{
ApiClient = client;
LogManager = new LogManager(config.LogLevel);
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);

_stateLock = new SemaphoreSlim(1, 1);
_restLogger = LogManager.CreateLogger("Rest");
_isFirstLogin = config.DisplayInitialLog;

}
internal void SetApiClient(API.DiscordRestApiClient client)
{
ApiClient = client;
ApiClient.RequestQueue.RateLimitTriggered += async (id, info) =>
{
if (info == null)


+ 9
- 13
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -1,10 +1,9 @@
#pragma warning disable CS1591
using Discord.API.Rest;
using Discord.Net;
using Discord.Net.Converters;
using Discord.Net.Queue;
using Discord.Net.Rest;
using Newtonsoft.Json;
using Discord.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -27,9 +26,9 @@ namespace Discord.API

public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } }
private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>();

protected readonly JsonSerializer _serializer;
protected readonly SemaphoreSlim _stateLock;
protected readonly ScopedSerializer _serializer;
private readonly RestClientProvider _restClientProvider;

protected bool _isDisposed;
@@ -45,13 +44,12 @@ namespace Discord.API
internal IRestClient RestClient { get; private set; }
internal ulong? CurrentUserId { get; set;}

public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry,
JsonSerializer serializer = null)
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, ScopedSerializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry)
{
_restClientProvider = restClientProvider;
UserAgent = userAgent;
_serializer = serializer;
DefaultRetryMode = defaultRetryMode;
_serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() };

RequestQueue = new RequestQueue();
_stateLock = new SemaphoreSlim(1, 1);
@@ -1159,16 +1157,14 @@ namespace Discord.API
protected string SerializeJson(object value)
{
var sb = new StringBuilder(256);
using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture))
using (JsonWriter writer = new JsonTextWriter(text))
_serializer.Serialize(writer, value);
using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture))
_serializer.ToJson(writer, value);
return sb.ToString();
}
protected T DeserializeJson<T>(Stream jsonStream)
{
using (TextReader text = new StreamReader(jsonStream))
using (JsonReader reader = new JsonTextReader(text))
return _serializer.Deserialize<T>(reader);
using (var reader = new StreamReader(jsonStream))
return _serializer.FromJson<T>(reader);
}

internal class BucketIds


+ 13
- 4
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Discord.Serialization;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Threading.Tasks;
@@ -7,15 +8,23 @@ namespace Discord.Rest
{
public class DiscordRestClient : BaseDiscordClient, IDiscordClient
{
private readonly ScopedSerializer _serializer;
private RestApplication _applicationInfo;

public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser;

public DiscordRestClient() : this(new DiscordRestConfig()) { }
public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { }
public DiscordRestClient(DiscordRestConfig config) : base(config)
{
_serializer = Serializer.CreateScope();
_serializer.Error += async ex =>
{
await _restLogger.WarningAsync("Serializer Error", ex);
};

SetApiClient(new API.DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent, _serializer, config.DefaultRetryMode));
}

private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent);
internal override void Dispose(bool disposing)
{
if (disposing)


+ 1
- 4
src/Discord.Net.Rest/Net/DefaultRestClient.cs View File

@@ -1,5 +1,4 @@
using Newtonsoft.Json;
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -18,7 +17,6 @@ namespace Discord.Net.Rest

private readonly HttpClient _client;
private readonly string _baseUrl;
private readonly JsonSerializer _errorDeserializer;
private CancellationToken _cancelToken;
private bool _isDisposed;

@@ -35,7 +33,6 @@ namespace Discord.Net.Rest
SetHeader("accept-encoding", "gzip, deflate");

_cancelToken = CancellationToken.None;
_errorDeserializer = new JsonSerializer();
}
private void Dispose(bool disposing)
{


src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs View File

@@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class ArrayConverter<T> : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs → src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs View File

@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class DiscordContractResolver : DefaultContractResolver
{

src/Discord.Net.Rest/Net/Converters/ImageConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs View File

@@ -2,7 +2,7 @@
using System;
using Model = Discord.API.Image;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class ImageConverter : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/NullableConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs View File

@@ -1,7 +1,7 @@
using Newtonsoft.Json;
using System;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class NullableConverter<T> : JsonConverter
where T : struct

src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs View File

@@ -1,7 +1,7 @@
using Newtonsoft.Json;
using System;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class OptionalConverter<T> : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs View File

@@ -1,7 +1,7 @@
using Newtonsoft.Json;
using System;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class PermissionTargetConverter : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs View File

@@ -1,7 +1,7 @@
using Newtonsoft.Json;
using System;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class StringEntityConverter : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs View File

@@ -2,7 +2,7 @@
using System;
using System.Globalization;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class UInt64Converter : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs View File

@@ -2,7 +2,7 @@
using System;
using System.Globalization;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class UInt64EntityConverter : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs View File

@@ -2,7 +2,7 @@
using Newtonsoft.Json;
using System;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class UInt64EntityOrIdConverter<T> : JsonConverter
{

src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs → src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs View File

@@ -1,7 +1,7 @@
using Newtonsoft.Json;
using System;

namespace Discord.Net.Converters
namespace Discord.Serialization.JsonConverters
{
internal class UserStatusConverter : JsonConverter
{

+ 80
- 0
src/Discord.Net.Rest/Serialization/Serializer.cs View File

@@ -0,0 +1,80 @@
using Discord.Serialization.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Serialization
{
internal class Serializer
{
public static ScopedSerializer Global { get; } = new ScopedSerializer();

public static T FromJson<T>(Stream stream) => Global.FromJson<T>(stream);
public static T FromJson<T>(StreamReader reader) => Global.FromJson<T>(reader);
public static T FromJson<T>(JsonTextReader reader) => Global.FromJson<T>(reader);
public static T FromJson<T>(JToken token) => Global.FromJson<T>(token);

public static void ToJson<T>(Stream stream, T obj) => Global.ToJson(stream, obj);
public static void ToJson<T>(StreamWriter writer, T obj) => Global.ToJson(writer, obj);
public static void ToJson<T>(JsonTextWriter writer, T obj) => Global.ToJson(writer, obj);

public static ScopedSerializer CreateScope() => new ScopedSerializer();
}

internal class ScopedSerializer
{
private readonly JsonSerializer _serializer;

private readonly AsyncEvent<Func<Exception, Task>> _errorEvent = new AsyncEvent<Func<Exception, Task>>();
public event Func<Exception, Task> Error
{
add { _errorEvent.Add(value); }
remove { _errorEvent.Remove(value); }
}

internal ScopedSerializer()
{
_serializer = new JsonSerializer
{
DateFormatString = "yyyy-MM-ddTHH:mm:ssZ",
ContractResolver = new DiscordContractResolver()
};
_serializer.Error += (s, e) =>
{
_errorEvent.InvokeAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
e.ErrorContext.Handled = true;
};
}

public T FromJson<T>(Stream stream)
{
using (var reader = new StreamReader(stream, Encoding.UTF8, false, 1024, true)) //1KB buffer
return FromJson<T>(reader);
}
public T FromJson<T>(TextReader reader)
{
using (var jsonReader = new JsonTextReader(reader) { CloseInput = false })
return FromJson<T>(jsonReader);
}
public T FromJson<T>(JsonTextReader reader)
=> _serializer.Deserialize<T>(reader);
public T FromJson<T>(JToken token)
=> token.ToObject<T>(_serializer);

public void ToJson<T>(Stream stream, T obj)
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, false)) //1KB buffer
ToJson(writer, obj);
}
public void ToJson<T>(TextWriter writer, T obj)
{
using (var jsonWriter = new JsonTextWriter(writer) { CloseOutput = false })
ToJson(jsonWriter, obj);
}
public void ToJson<T>(JsonTextWriter writer, T obj)
=> _serializer.Serialize(writer, obj);
}
}

+ 40
- 30
src/Discord.Net.Rpc/DiscordRpcApiClient.cs View File

@@ -4,6 +4,7 @@ using Discord.Net.Queue;
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Discord.Rpc;
using Discord.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -19,12 +20,13 @@ namespace Discord.API
{
internal class DiscordRpcApiClient : DiscordRestApiClient, IDisposable
{
private abstract class RpcRequest
private interface IRpcRequest
{
public abstract Task SetResultAsync(JToken data, JsonSerializer serializer);
public abstract Task SetExceptionAsync(JToken data, JsonSerializer serializer);
Task SetResultAsync(JToken data);
Task SetExceptionAsync(JToken data);
}
private class RpcRequest<T> : RpcRequest

private class RpcRequest<T> : IRpcRequest
{
public TaskCompletionSource<T> Promise { get; set; }

@@ -37,13 +39,13 @@ namespace Discord.API
Promise.TrySetCanceled(); //Doesn't need to be async, we're already in a separate task
});
}
public override Task SetResultAsync(JToken data, JsonSerializer serializer)
public Task SetResultAsync(JToken data)
{
return Promise.TrySetResultAsync(data.ToObject<T>(serializer));
return Promise.TrySetResultAsync(Serializer.FromJson<T>(data));
}
public override Task SetExceptionAsync(JToken data, JsonSerializer serializer)
public Task SetExceptionAsync(JToken data)
{
var error = data.ToObject<ErrorEvent>(serializer);
var error = Serializer.FromJson<ErrorEvent>(data);
return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message));
}
}
@@ -58,40 +60,46 @@ namespace Discord.API
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 ConcurrentDictionary<Guid, RpcRequest> _requests;
private readonly string _clientId;
private readonly ConcurrentDictionary<Guid, IRpcRequest> _requests;
private readonly IWebSocketClient _webSocketClient;
private readonly SemaphoreSlim _connectionLock;
private readonly string _clientId;
private readonly MemoryStream _decompressionStream;
private readonly StreamReader _decompressionReader;
private CancellationTokenSource _stateCancelToken;
private string _origin;

public ConnectionState ConnectionState { get; private set; }

public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider,
RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null)
: base(restClientProvider, userAgent, defaultRetryMode, serializer)
RetryMode defaultRetryMode = RetryMode.AlwaysRetry)
: base(restClientProvider, userAgent, defaultRetryMode)
{
_connectionLock = new SemaphoreSlim(1, 1);
_clientId = clientId;
_origin = origin;
_requests = new ConcurrentDictionary<Guid, RpcRequest>();
_requests = new ConcurrentDictionary<Guid, IRpcRequest>();

_decompressionStream = new MemoryStream(10 * 1024); //10 KB
_decompressionReader = new StreamReader(_decompressionStream);

_webSocketClient = webSocketProvider();
//_webSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+)
_webSocketClient.SetHeader("origin", _origin);
_webSocketClient.BinaryMessage += async (data, index, count) =>
{
using (var compressed = new MemoryStream(data, index + 2, count - 2))
using (var decompressed = new MemoryStream())
{
_decompressionStream.Position = 0;
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
zlib.CopyTo(decompressed);
decompressed.Position = 0;
using (var reader = new StreamReader(decompressed))
using (var jsonReader = new JsonTextReader(reader))
zlib.CopyTo(_decompressionStream);
_decompressionStream.SetLength(_decompressionStream.Position);

_decompressionStream.Position = 0;
var msg = _serializer.FromJson<API.Rpc.RpcFrame>(_decompressionReader);
if (msg != null)
{
var msg = _serializer.Deserialize<API.Rpc.RpcFrame>(jsonReader);
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false);
if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue)
ProcessMessage(msg);
@@ -101,12 +109,14 @@ namespace Discord.API
_webSocketClient.TextMessage += async text =>
{
using (var reader = new StringReader(text))
using (var jsonReader = new JsonTextReader(reader))
{
var msg = _serializer.Deserialize<API.Rpc.RpcFrame>(jsonReader);
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false);
if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue)
ProcessMessage(msg);
var msg = _serializer.FromJson<API.Rpc.RpcFrame>(reader);
if (msg != null)
{
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false);
if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue)
ProcessMessage(msg);
}
}
};
_webSocketClient.Closed += async ex =>
@@ -219,7 +229,7 @@ namespace Discord.API
payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid };
if (payload != null)
{
var json = SerializeJson(payload);
string json = SerializeJson(payload);
bytes = Encoding.UTF8.GetBytes(json);
}

@@ -249,7 +259,7 @@ namespace Discord.API
{
ClientId = _clientId,
Scopes = scopes,
RpcToken = rpcToken != null ? rpcToken : Optional.Create<string>()
RpcToken = rpcToken ?? Optional.Create<string>()
};
if (options.Timeout == null)
options.Timeout = 60000; //This requires manual input on the user's end, lets give them more time
@@ -378,15 +388,15 @@ namespace Discord.API

private bool ProcessMessage(API.Rpc.RpcFrame msg)
{
if (_requests.TryGetValue(msg.Nonce.Value.Value, out RpcRequest requestTracker))
if (_requests.TryGetValue(msg.Nonce.Value.Value, out IRpcRequest requestTracker))
{
if (msg.Event.GetValueOrDefault("") == "ERROR")
{
var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken, _serializer);
var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken);
}
else
{
var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken, _serializer);
var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken);
}
return true;
}


+ 30
- 29
src/Discord.Net.Rpc/DiscordRpcClient.cs View File

@@ -1,8 +1,6 @@
using Discord.API.Rpc;
using Discord.Logging;
using Discord.Net.Converters;
using Discord.Rest;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@@ -10,22 +8,23 @@ using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Discord.Serialization;

namespace Discord.Rpc
{
public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient
{
private readonly JsonSerializer _serializer;
private readonly ConnectionManager _connection;
private readonly Logger _rpcLogger;
private readonly SemaphoreSlim _stateLock, _authorizeLock;
private readonly ScopedSerializer _serializer;

public ConnectionState ConnectionState { get; private set; }
public IReadOnlyCollection<string> Scopes { get; private set; }
public DateTimeOffset TokenExpiresAt { get; private set; }

internal new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient;
public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } }
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; private set => base.CurrentUser = value; }
public RestApplication ApplicationInfo { get; private set; }

/// <summary> Creates a new RPC discord client. </summary>
@@ -38,18 +37,18 @@ namespace Discord.Rpc
_stateLock = new SemaphoreSlim(1, 1);
_authorizeLock = new SemaphoreSlim(1, 1);
_rpcLogger = LogManager.CreateLogger("RPC");

_serializer = Serializer.CreateScope();
_serializer.Error += async ex =>
{
await _rpcLogger.WarningAsync("Serializer Error", ex);
};

_connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout,
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
_connection.Connected += () => _connectedEvent.InvokeAsync();
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex);

_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer.Error += (s, e) =>
{
_rpcLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
e.ErrorContext.Handled = true;
};
ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
ApiClient.ReceivedRpcEvent += ProcessMessageAsync;
}
@@ -185,10 +184,12 @@ namespace Discord.Rpc
{
if (func == null) throw new NullReferenceException(nameof(func));

var settings = new VoiceProperties();
settings.Input = new VoiceDeviceProperties();
settings.Output = new VoiceDeviceProperties();
settings.Mode = new VoiceModeProperties();
var settings = new VoiceProperties
{
Input = new VoiceDeviceProperties(),
Output = new VoiceDeviceProperties(),
Mode = new VoiceModeProperties()
};
func(settings);

var model = new API.Rpc.VoiceSettings
@@ -294,9 +295,9 @@ namespace Discord.Rpc
case "READY":
{
await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<ReadyEvent>(_serializer);
var data = _serializer.FromJson<ReadyEvent>(payload.Value as JToken);

RequestOptions options = new RequestOptions
var options = new RequestOptions
{
//CancellationToken = _cancelToken //TODO: Implement
};
@@ -336,7 +337,7 @@ namespace Discord.Rpc
case "CHANNEL_CREATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<ChannelSummary>(_serializer);
var data = _serializer.FromJson<ChannelSummary>(payload.Value as JToken);
var channel = RpcChannelSummary.Create(data);

await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false);
@@ -347,7 +348,7 @@ namespace Discord.Rpc
case "GUILD_CREATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<GuildSummary>(_serializer);
var data = _serializer.FromJson<GuildSummary>(payload.Value as JToken);
var guild = RpcGuildSummary.Create(data);

await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false);
@@ -356,7 +357,7 @@ namespace Discord.Rpc
case "GUILD_STATUS":
{
await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<GuildStatusEvent>(_serializer);
var data = _serializer.FromJson<GuildStatusEvent>(payload.Value as JToken);
var guildStatus = RpcGuildStatus.Create(data);

await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false);
@@ -367,7 +368,7 @@ namespace Discord.Rpc
case "VOICE_STATE_CREATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
var data = _serializer.FromJson<ExtendedVoiceState>(payload.Value as JToken);
var voiceState = RpcVoiceState.Create(this, data);

await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
@@ -376,7 +377,7 @@ namespace Discord.Rpc
case "VOICE_STATE_UPDATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
var data = _serializer.FromJson<ExtendedVoiceState>(payload.Value as JToken);
var voiceState = RpcVoiceState.Create(this, data);

await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
@@ -385,7 +386,7 @@ namespace Discord.Rpc
case "VOICE_STATE_DELETE":
{
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
var data = _serializer.FromJson<ExtendedVoiceState>(payload.Value as JToken);
var voiceState = RpcVoiceState.Create(this, data);

await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
@@ -395,7 +396,7 @@ namespace Discord.Rpc
case "SPEAKING_START":
{
await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer);
var data = _serializer.FromJson<SpeakingEvent>(payload.Value as JToken);

await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false);
}
@@ -403,7 +404,7 @@ namespace Discord.Rpc
case "SPEAKING_STOP":
{
await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer);
var data = _serializer.FromJson<SpeakingEvent>(payload.Value as JToken);

await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false);
}
@@ -411,7 +412,7 @@ namespace Discord.Rpc
case "VOICE_SETTINGS_UPDATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<API.Rpc.VoiceSettings>(_serializer);
var data = _serializer.FromJson<API.Rpc.VoiceSettings>(payload.Value as JToken);
var settings = VoiceSettings.Create(data);

await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false);
@@ -422,7 +423,7 @@ namespace Discord.Rpc
case "MESSAGE_CREATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
var data = _serializer.FromJson<MessageEvent>(payload.Value as JToken);
var msg = RpcMessage.Create(this, data.ChannelId, data.Message);

await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false);
@@ -431,7 +432,7 @@ namespace Discord.Rpc
case "MESSAGE_UPDATE":
{
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
var data = _serializer.FromJson<MessageEvent>(payload.Value as JToken);
var msg = RpcMessage.Create(this, data.ChannelId, data.Message);

await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false);
@@ -440,7 +441,7 @@ namespace Discord.Rpc
case "MESSAGE_DELETE":
{
await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false);
var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
var data = _serializer.FromJson<MessageEvent>(payload.Value as JToken);

await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false);
}


+ 12
- 14
src/Discord.Net.WebSocket/Audio/AudioClient.cs View File

@@ -1,9 +1,7 @@
using Discord.API.Voice;
using Discord.Audio.Streams;
using Discord.Logging;
using Discord.Net.Converters;
using Discord.WebSocket;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
@@ -13,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Discord.Serialization;

namespace Discord.Audio
{
@@ -32,7 +31,7 @@ namespace Discord.Audio
}

private readonly Logger _audioLogger;
private readonly JsonSerializer _serializer;
private readonly ScopedSerializer _serializer;
private readonly ConnectionManager _connection;
private readonly SemaphoreSlim _stateLock;
private readonly ConcurrentQueue<long> _heartbeatTimes;
@@ -68,7 +67,13 @@ namespace Discord.Audio
ChannelId = channelId;
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}");

ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider);
_serializer = Serializer.CreateScope();
_serializer.Error += async ex =>
{
await _audioLogger.WarningAsync("Serializer Error", ex);
};

ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider, _serializer);
ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false);
//ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false);
@@ -85,13 +90,6 @@ namespace Discord.Audio
_ssrcMap = new ConcurrentDictionary<uint, ulong>();
_streams = new ConcurrentDictionary<ulong, StreamPair>();
_frameBuffers = new ConcurrentQueue<byte[]>();
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer.Error += (s, e) =>
{
_audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
e.ErrorContext.Handled = true;
};

LatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false);
UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false);
@@ -239,7 +237,7 @@ namespace Discord.Audio
case VoiceOpCode.Ready:
{
await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false);
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
var data = _serializer.FromJson<ReadyEvent>(payload as JToken);

_ssrc = data.SSRC;

@@ -255,7 +253,7 @@ namespace Discord.Audio
case VoiceOpCode.SessionDescription:
{
await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false);
var data = (payload as JToken).ToObject<SessionDescriptionEvent>(_serializer);
var data = _serializer.FromJson<SessionDescriptionEvent>(payload as JToken);

if (data.Mode != DiscordVoiceAPIClient.Mode)
throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}");
@@ -291,7 +289,7 @@ namespace Discord.Audio
{
await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false);

var data = (payload as JToken).ToObject<SpeakingEvent>(_serializer);
var data = _serializer.FromJson<SpeakingEvent>(payload as JToken);
_ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up

await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking);


+ 15
- 7
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Discord.Serialization;

namespace Discord.WebSocket
{
@@ -13,6 +14,8 @@ namespace Discord.WebSocket
{
private readonly DiscordSocketConfig _baseConfig;
private readonly SemaphoreSlim _connectionGroupLock;
private readonly ScopedSerializer _serializer;

private int[] _shardIds;
private Dictionary<int, int> _shardIdsToIndex;
private DiscordSocketClient[] _shards;
@@ -25,7 +28,7 @@ namespace Discord.WebSocket
public Game? Game => _shards[0].Game;

internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } }
public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; private set => base.CurrentUser = value; }
public IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount());
public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount());
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards;
@@ -34,13 +37,12 @@ namespace Discord.WebSocket
/// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { }
/// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { }
public DiscordShardedClient(DiscordSocketConfig config) : this(null, config) { }
/// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { }
/// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { }
private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client)
: base(config, client)
public DiscordShardedClient(int[] ids, DiscordSocketConfig config)
: base(config)
{
if (config.ShardId != null)
throw new ArgumentException($"{nameof(config.ShardId)} must not be set.");
@@ -52,6 +54,14 @@ namespace Discord.WebSocket
_baseConfig = config;
_connectionGroupLock = new SemaphoreSlim(1, 1);

_serializer = Serializer.CreateScope();
_serializer.Error += async ex =>
{
await _restLogger.WarningAsync("Serializer Error", ex);
};

SetApiClient(new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, _serializer));

if (config.TotalShards == null)
_automaticShards = true;
else
@@ -69,8 +79,6 @@ namespace Discord.WebSocket
}
}
}
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent);

internal override async Task OnLoginAsync(TokenType tokenType, string token)
{


+ 9
- 12
src/Discord.Net.WebSocket/DiscordSocketApiClient.cs View File

@@ -4,8 +4,8 @@ using Discord.API.Rest;
using Discord.Net.Queue;
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Discord.Serialization;
using Discord.WebSocket;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -36,13 +36,14 @@ namespace Discord.API

public ConnectionState ConnectionState { get; private set; }

public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent,
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null)
: base(restClientProvider, userAgent, defaultRetryMode, serializer)
public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, ScopedSerializer serializer,
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry)
: base(restClientProvider, userAgent, serializer, defaultRetryMode)
{
_gatewayUrl = url;
if (url != null)
_isExplicitUrl = true;

_decompressionStream = new MemoryStream(10 * 1024); //10 KB
_decompressionReader = new StreamReader(_decompressionStream);

@@ -58,20 +59,16 @@ namespace Discord.API
_decompressionStream.SetLength(_decompressionStream.Position);

_decompressionStream.Position = 0;
using (var jsonReader = new JsonTextReader(_decompressionReader) { CloseInput = false })
{
var msg = _serializer.Deserialize<SocketFrame>(jsonReader);
if (msg != null)
await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
}
var msg = _serializer.FromJson<SocketFrame>(_decompressionReader);
if (msg != null)
await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
}
};
WebSocketClient.TextMessage += async text =>
{
using (var reader = new StringReader(text))
using (var jsonReader = new JsonTextReader(reader))
{
var msg = _serializer.Deserialize<SocketFrame>(jsonReader);
var msg = _serializer.FromJson<SocketFrame>(reader);
if (msg != null)
await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
}


+ 48
- 52
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -1,11 +1,10 @@
using Discord.API;
using Discord.API.Gateway;
using Discord.Logging;
using Discord.Net.Converters;
using Discord.Net.Udp;
using Discord.Net.WebSockets;
using Discord.Rest;
using Newtonsoft.Json;
using Discord.Serialization;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
@@ -22,13 +21,12 @@ namespace Discord.WebSocket
public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient
{
private readonly ConcurrentQueue<ulong> _largeGuilds;
private readonly JsonSerializer _serializer;
private readonly SemaphoreSlim _connectionGroupLock;
private readonly SemaphoreSlim _connectionGroupLock, _stateLock;
private readonly ScopedSerializer _serializer;
private readonly DiscordSocketClient _parentClient;
private readonly ConcurrentQueue<long> _heartbeatTimes;
private readonly ConnectionManager _connection;
private readonly Logger _gatewayLogger;
private readonly SemaphoreSlim _stateLock;

private string _sessionId;
private int _lastSeq;
@@ -72,10 +70,9 @@ namespace Discord.WebSocket
/// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordSocketClient() : this(new DiscordSocketConfig()) { }
/// <summary> Creates a new REST/WebSocket discord client. </summary>
public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) { }
internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) { }
private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client, SemaphoreSlim groupLock, DiscordSocketClient parentClient)
: base(config, client)
public DiscordSocketClient(DiscordSocketConfig config) : this(config, null, null) { }
internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient)
: base(config)
{
ShardId = config.ShardId ?? 0;
TotalShards = config.TotalShards ?? 1;
@@ -87,9 +84,17 @@ namespace Discord.WebSocket
HandlerTimeout = config.HandlerTimeout;
State = new ClientState(0, 0);
_heartbeatTimes = new ConcurrentQueue<long>();

_stateLock = new SemaphoreSlim(1, 1);
_gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}");

_serializer = Serializer.CreateScope();
_serializer.Error += async ex =>
{
await _restLogger.WarningAsync("Serializer Error", ex);
};

SetApiClient(new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, _serializer, config.GatewayHost, config.DefaultRetryMode));

_connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout,
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
_connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected));
@@ -98,13 +103,6 @@ namespace Discord.WebSocket
_nextAudioId = 1;
_connectionGroupLock = groupLock;
_parentClient = parentClient;

_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer.Error += (s, e) =>
{
_gatewayLogger.WarningAsync("Serializer Error", e.ErrorContext.Error).GetAwaiter().GetResult();
e.ErrorContext.Handled = true;
};
ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
ApiClient.ReceivedGatewayEvent += ProcessMessageAsync;
@@ -127,8 +125,6 @@ namespace Discord.WebSocket
_voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>();
_largeGuilds = new ConcurrentQueue<ulong>();
}
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config)
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost);
internal override void Dispose(bool disposing)
{
if (disposing)
@@ -399,7 +395,7 @@ namespace Discord.WebSocket
case GatewayOpCode.Hello:
{
await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false);
var data = (payload as JToken).ToObject<HelloEvent>(_serializer);
var data = _serializer.FromJson<HelloEvent>(payload as JToken);

_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken);
}
@@ -455,7 +451,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
var data = _serializer.FromJson<ReadyEvent>(payload as JToken);
var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length);

var currentUser = SocketSelfUser.Create(this, state, data.User);
@@ -525,7 +521,7 @@ namespace Discord.WebSocket
//Guilds
case "GUILD_CREATE":
{
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
var data = _serializer.FromJson<ExtendedGuild>(payload as JToken);

if (data.Unavailable == false)
{
@@ -577,7 +573,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Guild>(_serializer);
var data = _serializer.FromJson<API.Guild>(payload as JToken);
var guild = State.GetGuild(data.Id);
if (guild != null)
{
@@ -596,7 +592,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer);
var data = _serializer.FromJson<API.Gateway.GuildEmojiUpdateEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -614,7 +610,7 @@ namespace Discord.WebSocket
case "GUILD_SYNC":
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false);
var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer);
var data = _serializer.FromJson<GuildSyncEvent>(payload as JToken);
var guild = State.GetGuild(data.Id);
if (guild != null)
{
@@ -635,7 +631,7 @@ namespace Discord.WebSocket
break;
case "GUILD_DELETE":
{
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
var data = _serializer.FromJson<ExtendedGuild>(payload as JToken);
if (data.Unavailable == true)
{
type = "GUILD_UNAVAILABLE";
@@ -677,7 +673,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var data = _serializer.FromJson<API.Channel>(payload as JToken);
SocketChannel channel = null;
if (data.GuildId.IsSpecified)
{
@@ -709,7 +705,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var data = _serializer.FromJson<API.Channel>(payload as JToken);
var channel = State.GetChannel(data.Id);
if (channel != null)
{
@@ -737,7 +733,7 @@ namespace Discord.WebSocket
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false);

SocketChannel channel = null;
var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var data = _serializer.FromJson<API.Channel>(payload as JToken);
if (data.GuildId.IsSpecified)
{
var guild = State.GetGuild(data.GuildId.Value);
@@ -775,7 +771,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer);
var data = _serializer.FromJson<GuildMemberAddEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -801,7 +797,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer);
var data = _serializer.FromJson<GuildMemberUpdateEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -839,7 +835,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer);
var data = _serializer.FromJson<GuildMemberRemoveEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -874,7 +870,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer);
var data = _serializer.FromJson<GuildMembersChunkEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -898,7 +894,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<RecipientEvent>(_serializer);
var data = _serializer.FromJson<RecipientEvent>(payload as JToken);
if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel)
{
var user = channel.GetOrAddUser(data.User);
@@ -915,7 +911,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<RecipientEvent>(_serializer);
var data = _serializer.FromJson<RecipientEvent>(payload as JToken);
if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel)
{
var user = channel.RemoveUser(data.User.Id);
@@ -940,7 +936,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer);
var data = _serializer.FromJson<GuildRoleCreateEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -964,7 +960,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer);
var data = _serializer.FromJson<GuildRoleUpdateEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -999,7 +995,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer);
var data = _serializer.FromJson<GuildRoleDeleteEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -1033,7 +1029,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer);
var data = _serializer.FromJson<GuildBanEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -1059,7 +1055,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer);
var data = _serializer.FromJson<GuildBanEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{
@@ -1087,7 +1083,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
var data = _serializer.FromJson<API.Message>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1134,7 +1130,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
var data = _serializer.FromJson<API.Message>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1181,7 +1177,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
var data = _serializer.FromJson<API.Message>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1208,7 +1204,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer);
var data = _serializer.FromJson<API.Gateway.Reaction>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage;
@@ -1232,7 +1228,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer);
var data = _serializer.FromJson<API.Gateway.Reaction>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage;
@@ -1256,7 +1252,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer);
var data = _serializer.FromJson<RemoveAllReactionsEvent>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage;
@@ -1278,7 +1274,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer);
var data = _serializer.FromJson<MessageDeleteBulkEvent>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1309,7 +1305,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Presence>(_serializer);
var data = _serializer.FromJson<API.Presence>(payload as JToken);

if (data.GuildId.IsSpecified)
{
@@ -1368,7 +1364,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer);
var data = _serializer.FromJson<TypingStartEvent>(payload as JToken);
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1390,7 +1386,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.User>(_serializer);
var data = _serializer.FromJson<API.User>(payload as JToken);
if (data.Id == CurrentUser.Id)
{
var before = CurrentUser.Clone();
@@ -1410,7 +1406,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.VoiceState>(_serializer);
var data = _serializer.FromJson<API.VoiceState>(payload as JToken);
SocketUser user;
SocketVoiceState before, after;
if (data.GuildId != null)
@@ -1482,7 +1478,7 @@ namespace Discord.WebSocket
{
await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer);
var data = _serializer.FromJson<VoiceServerUpdateEvent>(payload as JToken);
var guild = State.GetGuild(data.GuildId);
if (guild != null)
{


+ 14
- 20
src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs View File

@@ -1,9 +1,9 @@
#pragma warning disable CS1591
using Discord.API;
using Discord.API.Voice;
using Discord.Net.Converters;
using Discord.Net.Udp;
using Discord.Net.WebSockets;
using Discord.Serialization;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
@@ -37,7 +37,7 @@ namespace Discord.Audio
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 JsonSerializer _serializer;
private readonly ScopedSerializer _serializer;
private readonly SemaphoreSlim _connectionLock;
private readonly MemoryStream _decompressionStream;
private readonly StreamReader _decompressionReader;
@@ -52,12 +52,14 @@ namespace Discord.Audio

public ushort UdpPort => _udp.Port;

internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null)
internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, ScopedSerializer serializer)
{
GuildId = guildId;
_connectionLock = new SemaphoreSlim(1, 1);
_udp = udpSocketProvider();
_udp.ReceivedDatagram += (data, index, count) => _receivedPacketEvent.InvokeAsync(data, index, count);
_serializer = serializer;

_decompressionStream = new MemoryStream(10 * 1024); //10 KB
_decompressionReader = new StreamReader(_decompressionStream);

@@ -70,23 +72,19 @@ namespace Discord.Audio
_decompressionStream.Position = 0;
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
zlib.CopyTo(_decompressionStream);
_decompressionStream.SetLength(_decompressionStream.Position);

_decompressionStream.Position = 0;
using (var jsonReader = new JsonTextReader(_decompressionReader) { CloseInput = false })
{
var msg = _serializer.Deserialize<SocketFrame>(jsonReader);
if (msg != null)
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
}
_decompressionStream.SetLength(0);
var msg = _serializer.FromJson<SocketFrame>(_decompressionReader);
if (msg != null)
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
}
};
WebSocketClient.TextMessage += async text =>
{
using (var reader = new StringReader(text))
using (var jsonReader = new JsonTextReader(reader))
{
var msg = _serializer.Deserialize<SocketFrame>(jsonReader);
var msg = _serializer.FromJson<SocketFrame>(reader);
if (msg != null)
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
}
@@ -96,8 +94,6 @@ namespace Discord.Audio
await DisconnectAsync().ConfigureAwait(false);
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
};

_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
}
private void Dispose(bool disposing)
{
@@ -259,16 +255,14 @@ namespace Discord.Audio
private string SerializeJson(object value)
{
var sb = new StringBuilder(256);
using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture))
using (JsonWriter writer = new JsonTextWriter(text))
_serializer.Serialize(writer, value);
using (TextWriter writer = new StringWriter(sb, CultureInfo.InvariantCulture))
_serializer.ToJson(writer, value);
return sb.ToString();
}
private T DeserializeJson<T>(Stream jsonStream)
{
using (TextReader text = new StreamReader(jsonStream))
using (JsonReader reader = new JsonTextReader(text))
return _serializer.Deserialize<T>(reader);
using (TextReader reader = new StreamReader(jsonStream))
return _serializer.FromJson<T>(reader);
}
}
}

Loading…
Cancel
Save