diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index aa822f99e..91a614cf8 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -3,7 +3,7 @@ public static class Format { // Characters which need escaping - private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; + private static string[] _sensitiveCharacters = { "\\", "*", "_", "~", "`" }; /// Returns a markdown-formatted string with bold formatting. public static string Bold(string text) => $"**{text}**"; @@ -26,7 +26,7 @@ /// Sanitizes the string, safely escaping any Markdown sequences. public static string Sanitize(string text) { - foreach (string unsafeChar in SensitiveCharacters) + foreach (string unsafeChar in _sensitiveCharacters) text = text.Replace(unsafeChar, $"\\{unsafeChar}"); return text; } diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index ed12ff383..405b47274 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -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; /// Creates a new REST-only discord client. - 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) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 1fac66ec5..5fb5e00ac 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -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 SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); - - 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(Stream jsonStream) { - using (TextReader text = new StreamReader(jsonStream)) - using (JsonReader reader = new JsonTextReader(text)) - return _serializer.Deserialize(reader); + using (var reader = new StreamReader(jsonStream)) + return _serializer.FromJson(reader); } internal class BucketIds diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index aa9937008..e71339eff 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -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) diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index a54107829..d9211722d 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -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) { diff --git a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs similarity index 97% rename from src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs index 3cededb7b..a6b09eb51 100644 --- a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class ArrayConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs similarity index 99% rename from src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs index b465fbed2..4345f37e1 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs @@ -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 { diff --git a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs similarity index 96% rename from src/Discord.Net.Rest/Net/Converters/ImageConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs index f4d591d7e..aa3b1c167 100644 --- a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs @@ -2,7 +2,7 @@ using System; using Model = Discord.API.Image; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class ImageConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/NullableConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs similarity index 97% rename from src/Discord.Net.Rest/Net/Converters/NullableConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs index 0b149e725..4bab750fa 100644 --- a/src/Discord.Net.Rest/Net/Converters/NullableConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using System; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class NullableConverter : JsonConverter where T : struct diff --git a/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs similarity index 96% rename from src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs index 18b2a9e1c..b3b75331f 100644 --- a/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using System; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class OptionalConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs similarity index 96% rename from src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs index 0ed566a84..4f01436a8 100644 --- a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using System; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class PermissionTargetConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs similarity index 94% rename from src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs index d7dd58d71..fe7bd9af5 100644 --- a/src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using System; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class StringEntityConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs similarity index 94% rename from src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs index 27cbe9290..2b5b1724a 100644 --- a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs @@ -2,7 +2,7 @@ using System; using System.Globalization; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class UInt64Converter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs similarity index 95% rename from src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs index b8d8f1057..6726d1344 100644 --- a/src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs @@ -2,7 +2,7 @@ using System; using System.Globalization; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class UInt64EntityConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs similarity index 96% rename from src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs index ae8cf2cb2..fb599e80f 100644 --- a/src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class UInt64EntityOrIdConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs similarity index 97% rename from src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs rename to src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs index c0a287c16..77ecf53ba 100644 --- a/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using System; -namespace Discord.Net.Converters +namespace Discord.Serialization.JsonConverters { internal class UserStatusConverter : JsonConverter { diff --git a/src/Discord.Net.Rest/Serialization/Serializer.cs b/src/Discord.Net.Rest/Serialization/Serializer.cs new file mode 100644 index 000000000..bbcce8bc3 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Serializer.cs @@ -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(Stream stream) => Global.FromJson(stream); + public static T FromJson(StreamReader reader) => Global.FromJson(reader); + public static T FromJson(JsonTextReader reader) => Global.FromJson(reader); + public static T FromJson(JToken token) => Global.FromJson(token); + + public static void ToJson(Stream stream, T obj) => Global.ToJson(stream, obj); + public static void ToJson(StreamWriter writer, T obj) => Global.ToJson(writer, obj); + public static void ToJson(JsonTextWriter writer, T obj) => Global.ToJson(writer, obj); + + public static ScopedSerializer CreateScope() => new ScopedSerializer(); + } + + internal class ScopedSerializer + { + private readonly JsonSerializer _serializer; + + private readonly AsyncEvent> _errorEvent = new AsyncEvent>(); + public event Func 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(Stream stream) + { + using (var reader = new StreamReader(stream, Encoding.UTF8, false, 1024, true)) //1KB buffer + return FromJson(reader); + } + public T FromJson(TextReader reader) + { + using (var jsonReader = new JsonTextReader(reader) { CloseInput = false }) + return FromJson(jsonReader); + } + public T FromJson(JsonTextReader reader) + => _serializer.Deserialize(reader); + public T FromJson(JToken token) + => token.ToObject(_serializer); + + public void ToJson(Stream stream, T obj) + { + using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, false)) //1KB buffer + ToJson(writer, obj); + } + public void ToJson(TextWriter writer, T obj) + { + using (var jsonWriter = new JsonTextWriter(writer) { CloseOutput = false }) + ToJson(jsonWriter, obj); + } + public void ToJson(JsonTextWriter writer, T obj) + => _serializer.Serialize(writer, obj); + } +} diff --git a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs index 50d467054..019dbd635 100644 --- a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs @@ -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 : RpcRequest + + private class RpcRequest : IRpcRequest { public TaskCompletionSource 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(serializer)); + return Promise.TrySetResultAsync(Serializer.FromJson(data)); } - public override Task SetExceptionAsync(JToken data, JsonSerializer serializer) + public Task SetExceptionAsync(JToken data) { - var error = data.ToObject(serializer); + var error = Serializer.FromJson(data); return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); } } @@ -58,40 +60,46 @@ namespace Discord.API public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); - private readonly ConcurrentDictionary _requests; + private readonly string _clientId; + private readonly ConcurrentDictionary _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(); - + _requests = new ConcurrentDictionary(); + + _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(_decompressionReader); + if (msg != null) { - var msg = _serializer.Deserialize(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(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(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() + RpcToken = rpcToken ?? Optional.Create() }; 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; } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 9c77fc919..dba477808 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -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 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; } /// Creates a new RPC discord client. @@ -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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(payload.Value as JToken); await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 1ed5c3851..dc0b07373 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -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 _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(); _streams = new ConcurrentDictionary(); _frameBuffers = new ConcurrentQueue(); - - _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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(payload as JToken); _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index ab2cb9266..9d199ed69 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -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 _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 Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); public IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); public IReadOnlyCollection Shards => _shards; @@ -34,13 +37,12 @@ namespace Discord.WebSocket /// Creates a new REST/WebSocket discord client. public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } /// Creates a new REST/WebSocket discord client. - public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { } + public DiscordShardedClient(DiscordSocketConfig config) : this(null, config) { } /// Creates a new REST/WebSocket discord client. public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } /// Creates a new REST/WebSocket discord client. - 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) { diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index aff3f30a3..5355324ea 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -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(jsonReader); - if (msg != null) - await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); - } + var msg = _serializer.FromJson(_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(jsonReader); + var msg = _serializer.FromJson(reader); if (msg != null) await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index b13ceca1d..11b21b9df 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -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 _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 _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 /// Creates a new REST/WebSocket discord client. public DiscordSocketClient() : this(new DiscordSocketConfig()) { } /// Creates a new REST/WebSocket discord client. - 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(); - _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(); _largeGuilds = new ConcurrentQueue(); } - 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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(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(_serializer); + var data = _serializer.FromJson(payload as JToken); var guild = State.GetGuild(data.GuildId); if (guild != null) { diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 764bf5807..7817ef81c 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -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 Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); - 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(jsonReader); - if (msg != null) - await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); - } - _decompressionStream.SetLength(0); + var msg = _serializer.FromJson(_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(jsonReader); + var msg = _serializer.FromJson(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(Stream jsonStream) { - using (TextReader text = new StreamReader(jsonStream)) - using (JsonReader reader = new JsonTextReader(text)) - return _serializer.Deserialize(reader); + using (TextReader reader = new StreamReader(jsonStream)) + return _serializer.FromJson(reader); } } }