From ed32790cf54bed752fd442aeeb563dda98bc83b5 Mon Sep 17 00:00:00 2001 From: Jarl Gullberg Date: Mon, 18 Dec 2017 20:28:45 +0100 Subject: [PATCH 1/2] Add new mono-compatible websocket provider. --- Discord.Net.sln | 17 +- ...iscord.Net.Providers.WebSocketSharp.csproj | 15 ++ .../WebSocketSharpClient.cs | 223 ++++++++++++++++++ .../WebSocketSharpProvider.cs | 9 + 4 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj create mode 100644 src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs create mode 100644 src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index 58bfcad86..38b3509f7 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.12 MinimumVisualStudioVersion = 10.0.40219.1 @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Providers.WebSocketSharp", "src\Discord.Net.Providers.WebSocketSharp\Discord.Net.Providers.WebSocketSharp.csproj", "{0748E83F-87A1-4C07-B458-6E438841925B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -130,6 +132,18 @@ Global {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x64.ActiveCfg = Debug|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x64.Build.0 = Debug|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x86.Build.0 = Debug|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Release|Any CPU.Build.0 = Release|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x64.ActiveCfg = Release|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x64.Build.0 = Release|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x86.ActiveCfg = Release|Any CPU + {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,6 +155,7 @@ Global {688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} + {0748E83F-87A1-4C07-B458-6E438841925B} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} diff --git a/src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj b/src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj new file mode 100644 index 000000000..50db25efd --- /dev/null +++ b/src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj @@ -0,0 +1,15 @@ + + + + Discord.Net.Providers.WebSocketSharp + Discord.Providers.WebSocketSharp + An optional WebSocket client provider for Discord.Net using websocket-sharp + netstandard2.0 + + + + + + + + \ No newline at end of file diff --git a/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs new file mode 100644 index 000000000..40aeed644 --- /dev/null +++ b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Authentication; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Discord.Net; +using Discord.Net.WebSockets; +using WebSocketSharp; + +namespace Discord.Providers.WebSocketSharp +{ + /// + /// WebSocket provider using websocket-sharp. + /// + internal class WebSocketSharpClient : IWebSocketClient, IDisposable + { + /// + public event Func BinaryMessage; + + /// + public event Func TextMessage; + + /// + public event Func Closed; + + private readonly SemaphoreSlim _lock; + private readonly Dictionary _headers; + private readonly ManualResetEventSlim _waitUntilConnect; + + private WebSocket _client; + private CancellationTokenSource _cancelTokenSource; + private CancellationToken _cancelToken; + private CancellationToken _parentToken; + + private bool _isDisposed; + + /// + /// Initializes a new instance of the class. + /// + public WebSocketSharpClient() + { + _headers = new Dictionary(); + _lock = new SemaphoreSlim(1, 1); + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationToken.None; + _parentToken = CancellationToken.None; + _waitUntilConnect = new ManualResetEventSlim(); + } + + /// + public void SetHeader(string key, string value) + { + _headers[key] = value; + } + + /// + public void SetCancelToken(CancellationToken cancelToken) + { + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource + ( + _parentToken, + _cancelTokenSource.Token + ) + .Token; + } + + /// + public async Task ConnectAsync(string host) + { + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await ConnectInternalAsync(host).ConfigureAwait(false); + } + finally + { + _lock.Release(); + } + } + + private async Task ConnectInternalAsync(string host) + { + await DisconnectInternalAsync().ConfigureAwait(false); + + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource + ( + _parentToken, + _cancelTokenSource.Token + ) + .Token; + + _client = new WebSocket(host) + { + CustomHeaders = _headers.ToList() + }; + _client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; + + _client.OnMessage += OnMessage; + _client.OnOpen += OnConnected; + _client.OnClose += OnClosed; + + _client.Connect(); + _waitUntilConnect.Wait(_cancelToken); + } + + /// + public async Task DisconnectAsync() + { + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally + { + _lock.Release(); + } + } + + private Task DisconnectInternalAsync() + { + _cancelTokenSource.Cancel(); + if (_client is null) + { + return Task.CompletedTask; + } + + if (_client.ReadyState == WebSocketState.Open) + { + _client.Close(); + } + + _client.OnMessage -= OnMessage; + _client.OnOpen -= OnConnected; + _client.OnClose -= OnClosed; + + _client = null; + _waitUntilConnect.Reset(); + + return Task.CompletedTask; + } + + private void OnMessage(object sender, MessageEventArgs messageEventArgs) + { + if (messageEventArgs.IsBinary) + { + OnBinaryMessage(messageEventArgs); + } + else if (messageEventArgs.IsText) + { + OnTextMessage(messageEventArgs); + } + } + + /// + public async Task SendAsync(byte[] data, int index, int count, bool isText) + { + await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); + try + { + if (isText) + { + _client.Send(Encoding.UTF8.GetString(data, index, count)); + } + else + { + _client.Send(data.Skip(index).Take(count).ToArray()); + } + } + finally + { + _lock.Release(); + } + } + + private void OnTextMessage(MessageEventArgs e) + { + TextMessage?.Invoke(e.Data).GetAwaiter().GetResult(); + } + + private void OnBinaryMessage(MessageEventArgs e) + { + BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult(); + } + + private void OnConnected(object sender, EventArgs e) + { + _waitUntilConnect.Set(); + } + + private void OnClosed(object sender, CloseEventArgs e) + { + if (e.WasClean) + { + Closed?.Invoke(null).GetAwaiter().GetResult(); + return; + } + + var ex = new WebSocketClosedException(e.Code, e.Reason); + Closed?.Invoke(ex).GetAwaiter().GetResult(); + } + + /// + public void Dispose() + { + if (_isDisposed) + { + return; + } + + DisconnectInternalAsync().GetAwaiter().GetResult(); + + ((IDisposable)_client)?.Dispose(); + _client = null; + + _isDisposed = true; + } + } +} diff --git a/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs new file mode 100644 index 000000000..e200d3c0c --- /dev/null +++ b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs @@ -0,0 +1,9 @@ +using Discord.Net.WebSockets; + +namespace Discord.Providers.WebSocketSharp +{ + public static class WebSocketSharpProvider + { + public static readonly WebSocketProvider Instance = () => new WebSocketSharpClient(); + } +} From 4d50f7c3421120d0d38650a3e5dcd45c43338194 Mon Sep 17 00:00:00 2001 From: Jarl Gullberg Date: Mon, 18 Dec 2017 20:33:20 +0100 Subject: [PATCH 2/2] Replace tabs with spaces. --- .../WebSocketSharpClient.cs | 416 +++++++++--------- 1 file changed, 208 insertions(+), 208 deletions(-) diff --git a/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs index 40aeed644..1bbb63fde 100644 --- a/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs +++ b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs @@ -12,212 +12,212 @@ using WebSocketSharp; namespace Discord.Providers.WebSocketSharp { /// - /// WebSocket provider using websocket-sharp. - /// - internal class WebSocketSharpClient : IWebSocketClient, IDisposable - { - /// - public event Func BinaryMessage; - - /// - public event Func TextMessage; - - /// - public event Func Closed; - - private readonly SemaphoreSlim _lock; - private readonly Dictionary _headers; - private readonly ManualResetEventSlim _waitUntilConnect; - - private WebSocket _client; - private CancellationTokenSource _cancelTokenSource; - private CancellationToken _cancelToken; - private CancellationToken _parentToken; - - private bool _isDisposed; - - /// - /// Initializes a new instance of the class. - /// - public WebSocketSharpClient() - { - _headers = new Dictionary(); - _lock = new SemaphoreSlim(1, 1); - _cancelTokenSource = new CancellationTokenSource(); - _cancelToken = CancellationToken.None; - _parentToken = CancellationToken.None; - _waitUntilConnect = new ManualResetEventSlim(); - } - - /// - public void SetHeader(string key, string value) - { - _headers[key] = value; - } - - /// - public void SetCancelToken(CancellationToken cancelToken) - { - _parentToken = cancelToken; - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource - ( - _parentToken, - _cancelTokenSource.Token - ) - .Token; - } - - /// - public async Task ConnectAsync(string host) - { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await ConnectInternalAsync(host).ConfigureAwait(false); - } - finally - { - _lock.Release(); - } - } - - private async Task ConnectInternalAsync(string host) - { - await DisconnectInternalAsync().ConfigureAwait(false); - - _cancelTokenSource = new CancellationTokenSource(); - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource - ( - _parentToken, - _cancelTokenSource.Token - ) - .Token; - - _client = new WebSocket(host) - { - CustomHeaders = _headers.ToList() - }; - _client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; - - _client.OnMessage += OnMessage; - _client.OnOpen += OnConnected; - _client.OnClose += OnClosed; - - _client.Connect(); - _waitUntilConnect.Wait(_cancelToken); - } - - /// - public async Task DisconnectAsync() - { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectInternalAsync().ConfigureAwait(false); - } - finally - { - _lock.Release(); - } - } - - private Task DisconnectInternalAsync() - { - _cancelTokenSource.Cancel(); - if (_client is null) - { - return Task.CompletedTask; - } - - if (_client.ReadyState == WebSocketState.Open) - { - _client.Close(); - } - - _client.OnMessage -= OnMessage; - _client.OnOpen -= OnConnected; - _client.OnClose -= OnClosed; - - _client = null; - _waitUntilConnect.Reset(); - - return Task.CompletedTask; - } - - private void OnMessage(object sender, MessageEventArgs messageEventArgs) - { - if (messageEventArgs.IsBinary) - { - OnBinaryMessage(messageEventArgs); - } - else if (messageEventArgs.IsText) - { - OnTextMessage(messageEventArgs); - } - } - - /// - public async Task SendAsync(byte[] data, int index, int count, bool isText) - { - await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); - try - { - if (isText) - { - _client.Send(Encoding.UTF8.GetString(data, index, count)); - } - else - { - _client.Send(data.Skip(index).Take(count).ToArray()); - } - } - finally - { - _lock.Release(); - } - } - - private void OnTextMessage(MessageEventArgs e) - { - TextMessage?.Invoke(e.Data).GetAwaiter().GetResult(); - } - - private void OnBinaryMessage(MessageEventArgs e) - { - BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult(); - } - - private void OnConnected(object sender, EventArgs e) - { - _waitUntilConnect.Set(); - } - - private void OnClosed(object sender, CloseEventArgs e) - { - if (e.WasClean) - { - Closed?.Invoke(null).GetAwaiter().GetResult(); - return; - } - - var ex = new WebSocketClosedException(e.Code, e.Reason); - Closed?.Invoke(ex).GetAwaiter().GetResult(); - } - - /// - public void Dispose() - { - if (_isDisposed) - { - return; - } - - DisconnectInternalAsync().GetAwaiter().GetResult(); - - ((IDisposable)_client)?.Dispose(); - _client = null; - - _isDisposed = true; - } - } + /// WebSocket provider using websocket-sharp. + /// + internal class WebSocketSharpClient : IWebSocketClient, IDisposable + { + /// + public event Func BinaryMessage; + + /// + public event Func TextMessage; + + /// + public event Func Closed; + + private readonly SemaphoreSlim _lock; + private readonly Dictionary _headers; + private readonly ManualResetEventSlim _waitUntilConnect; + + private WebSocket _client; + private CancellationTokenSource _cancelTokenSource; + private CancellationToken _cancelToken; + private CancellationToken _parentToken; + + private bool _isDisposed; + + /// + /// Initializes a new instance of the class. + /// + public WebSocketSharpClient() + { + _headers = new Dictionary(); + _lock = new SemaphoreSlim(1, 1); + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationToken.None; + _parentToken = CancellationToken.None; + _waitUntilConnect = new ManualResetEventSlim(); + } + + /// + public void SetHeader(string key, string value) + { + _headers[key] = value; + } + + /// + public void SetCancelToken(CancellationToken cancelToken) + { + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource + ( + _parentToken, + _cancelTokenSource.Token + ) + .Token; + } + + /// + public async Task ConnectAsync(string host) + { + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await ConnectInternalAsync(host).ConfigureAwait(false); + } + finally + { + _lock.Release(); + } + } + + private async Task ConnectInternalAsync(string host) + { + await DisconnectInternalAsync().ConfigureAwait(false); + + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource + ( + _parentToken, + _cancelTokenSource.Token + ) + .Token; + + _client = new WebSocket(host) + { + CustomHeaders = _headers.ToList() + }; + _client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; + + _client.OnMessage += OnMessage; + _client.OnOpen += OnConnected; + _client.OnClose += OnClosed; + + _client.Connect(); + _waitUntilConnect.Wait(_cancelToken); + } + + /// + public async Task DisconnectAsync() + { + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally + { + _lock.Release(); + } + } + + private Task DisconnectInternalAsync() + { + _cancelTokenSource.Cancel(); + if (_client is null) + { + return Task.CompletedTask; + } + + if (_client.ReadyState == WebSocketState.Open) + { + _client.Close(); + } + + _client.OnMessage -= OnMessage; + _client.OnOpen -= OnConnected; + _client.OnClose -= OnClosed; + + _client = null; + _waitUntilConnect.Reset(); + + return Task.CompletedTask; + } + + private void OnMessage(object sender, MessageEventArgs messageEventArgs) + { + if (messageEventArgs.IsBinary) + { + OnBinaryMessage(messageEventArgs); + } + else if (messageEventArgs.IsText) + { + OnTextMessage(messageEventArgs); + } + } + + /// + public async Task SendAsync(byte[] data, int index, int count, bool isText) + { + await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); + try + { + if (isText) + { + _client.Send(Encoding.UTF8.GetString(data, index, count)); + } + else + { + _client.Send(data.Skip(index).Take(count).ToArray()); + } + } + finally + { + _lock.Release(); + } + } + + private void OnTextMessage(MessageEventArgs e) + { + TextMessage?.Invoke(e.Data).GetAwaiter().GetResult(); + } + + private void OnBinaryMessage(MessageEventArgs e) + { + BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult(); + } + + private void OnConnected(object sender, EventArgs e) + { + _waitUntilConnect.Set(); + } + + private void OnClosed(object sender, CloseEventArgs e) + { + if (e.WasClean) + { + Closed?.Invoke(null).GetAwaiter().GetResult(); + return; + } + + var ex = new WebSocketClosedException(e.Code, e.Reason); + Closed?.Invoke(ex).GetAwaiter().GetResult(); + } + + /// + public void Dispose() + { + if (_isDisposed) + { + return; + } + + DisconnectInternalAsync().GetAwaiter().GetResult(); + + ((IDisposable)_client)?.Dispose(); + _client = null; + + _isDisposed = true; + } + } }