feature: Implement Dispose for types which have disposable data (#1171)
* Initial set of dispose implementations Not handled yet: - Discord.Net.Websocket/Entities/SocketGuild - Discord.Net.Tests * Refactor DiscordSocketClient init into ctor This way we remove an IDisposableAnalyzer warning for not disposing the client when we set the client variable. * Dispose of clients when disposing sharded client * Finish implementing IDisposable where appropriate I opted to use NoWarn in the Tests project as it wasn't really necessary considering that our tests only run once * Tweak samples after feedback
This commit is contained in:
@@ -16,21 +16,28 @@ namespace _01_basic_ping_bot
|
|||||||
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
|
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
private DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
// Discord.Net heavily utilizes TAP for async, so we create
|
// Discord.Net heavily utilizes TAP for async, so we create
|
||||||
// an asynchronous context from the beginning.
|
// an asynchronous context from the beginning.
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
=> new Program().MainAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
public async Task MainAsync()
|
|
||||||
{
|
{
|
||||||
|
new Program().MainAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Program()
|
||||||
|
{
|
||||||
|
// It is recommended to Dispose of a client when you are finished
|
||||||
|
// using it, at the end of your app's lifetime.
|
||||||
_client = new DiscordSocketClient();
|
_client = new DiscordSocketClient();
|
||||||
|
|
||||||
_client.Log += LogAsync;
|
_client.Log += LogAsync;
|
||||||
_client.Ready += ReadyAsync;
|
_client.Ready += ReadyAsync;
|
||||||
_client.MessageReceived += MessageReceivedAsync;
|
_client.MessageReceived += MessageReceivedAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MainAsync()
|
||||||
|
{
|
||||||
// Tokens should be considered secret data, and never hard-coded.
|
// Tokens should be considered secret data, and never hard-coded.
|
||||||
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
||||||
await _client.StartAsync();
|
await _client.StartAsync();
|
||||||
|
|||||||
@@ -19,24 +19,32 @@ namespace _02_commands_framework
|
|||||||
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
|
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
|
// There is no need to implement IDisposable like before as we are
|
||||||
|
// using dependency injection, which handles calling Dispose for us.
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
=> new Program().MainAsync().GetAwaiter().GetResult();
|
=> new Program().MainAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
public async Task MainAsync()
|
public async Task MainAsync()
|
||||||
{
|
{
|
||||||
var services = ConfigureServices();
|
// You should dispose a service provider created using ASP.NET
|
||||||
|
// when you are finished using it, at the end of your app's lifetime.
|
||||||
|
// If you use another dependency injection framework, you should inspect
|
||||||
|
// its documentation for the best way to do this.
|
||||||
|
using (var services = ConfigureServices())
|
||||||
|
{
|
||||||
|
var client = services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
|
||||||
var client = services.GetRequiredService<DiscordSocketClient>();
|
client.Log += LogAsync;
|
||||||
|
services.GetRequiredService<CommandService>().Log += LogAsync;
|
||||||
|
|
||||||
client.Log += LogAsync;
|
// Tokens should be considered secret data, and never hard-coded.
|
||||||
services.GetRequiredService<CommandService>().Log += LogAsync;
|
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
|
||||||
await client.StartAsync();
|
|
||||||
|
|
||||||
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
|
await Task.Delay(-1);
|
||||||
|
}
|
||||||
await Task.Delay(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task LogAsync(LogMessage log)
|
private Task LogAsync(LogMessage log)
|
||||||
@@ -46,7 +54,7 @@ namespace _02_commands_framework
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceProvider ConfigureServices()
|
private ServiceProvider ConfigureServices()
|
||||||
{
|
{
|
||||||
return new ServiceCollection()
|
return new ServiceCollection()
|
||||||
.AddSingleton<DiscordSocketClient>()
|
.AddSingleton<DiscordSocketClient>()
|
||||||
|
|||||||
@@ -13,41 +13,46 @@ namespace _03_sharded_client
|
|||||||
// DiscordSocketClient instances (or shards) to serve a large number of guilds.
|
// DiscordSocketClient instances (or shards) to serve a large number of guilds.
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
private DiscordShardedClient _client;
|
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
=> new Program().MainAsync().GetAwaiter().GetResult();
|
=> new Program().MainAsync().GetAwaiter().GetResult();
|
||||||
public async Task MainAsync()
|
public async Task MainAsync()
|
||||||
{
|
{
|
||||||
// You specify the amount of shards you'd like to have with the
|
// You specify the amount of shards you'd like to have with the
|
||||||
// DiscordSocketConfig. Generally, it's recommended to
|
// DiscordSocketConfig. Generally, it's recommended to
|
||||||
// have 1 shard per 1500-2000 guilds your bot is in.
|
// have 1 shard per 1500-2000 guilds your bot is in.
|
||||||
var config = new DiscordSocketConfig
|
var config = new DiscordSocketConfig
|
||||||
{
|
{
|
||||||
TotalShards = 2
|
TotalShards = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
_client = new DiscordShardedClient(config);
|
// You should dispose a service provider created using ASP.NET
|
||||||
var services = ConfigureServices();
|
// when you are finished using it, at the end of your app's lifetime.
|
||||||
|
// If you use another dependency injection framework, you should inspect
|
||||||
|
// its documentation for the best way to do this.
|
||||||
|
using (var services = ConfigureServices(config))
|
||||||
|
{
|
||||||
|
var client = services.GetRequiredService<DiscordShardedClient>();
|
||||||
|
|
||||||
// The Sharded Client does not have a Ready event.
|
// The Sharded Client does not have a Ready event.
|
||||||
// The ShardReady event is used instead, allowing for individual
|
// The ShardReady event is used instead, allowing for individual
|
||||||
// control per shard.
|
// control per shard.
|
||||||
_client.ShardReady += ReadyAsync;
|
client.ShardReady += ReadyAsync;
|
||||||
_client.Log += LogAsync;
|
client.Log += LogAsync;
|
||||||
|
|
||||||
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
|
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
|
||||||
|
|
||||||
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
// Tokens should be considered secret data, and never hard-coded.
|
||||||
await _client.StartAsync();
|
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
await Task.Delay(-1);
|
await Task.Delay(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceProvider ConfigureServices()
|
private ServiceProvider ConfigureServices(DiscordSocketConfig config)
|
||||||
{
|
{
|
||||||
return new ServiceCollection()
|
return new ServiceCollection()
|
||||||
.AddSingleton(_client)
|
.AddSingleton(new DiscordShardedClient(config))
|
||||||
.AddSingleton<CommandService>()
|
.AddSingleton<CommandService>()
|
||||||
.AddSingleton<CommandHandlingService>()
|
.AddSingleton<CommandHandlingService>()
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Discord.Commands
|
|||||||
/// been successfully executed.
|
/// been successfully executed.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class CommandService
|
public class CommandService : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a command-related information is received.
|
/// Occurs when a command-related information is received.
|
||||||
@@ -67,6 +67,8 @@ namespace Discord.Commands
|
|||||||
internal readonly LogManager _logManager;
|
internal readonly LogManager _logManager;
|
||||||
internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap;
|
internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap;
|
||||||
|
|
||||||
|
internal bool _isDisposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents all modules loaded within <see cref="CommandService"/>.
|
/// Represents all modules loaded within <see cref="CommandService"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -330,9 +332,9 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
//Type Readers
|
//Type Readers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
|
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
|
||||||
/// type.
|
/// type.
|
||||||
/// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
|
/// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
|
||||||
/// also be added.
|
/// also be added.
|
||||||
/// If a default <see cref="TypeReader" /> exists for <typeparamref name="T" />, a warning will be logged
|
/// If a default <see cref="TypeReader" /> exists for <typeparamref name="T" />, a warning will be logged
|
||||||
/// and the default <see cref="TypeReader" /> will be replaced.
|
/// and the default <see cref="TypeReader" /> will be replaced.
|
||||||
@@ -607,5 +609,23 @@ namespace Discord.Commands
|
|||||||
await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result);
|
await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_moduleLock?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,4 @@
|
|||||||
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard2.0' ">
|
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard2.0' ">
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -12,4 +12,7 @@
|
|||||||
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
|
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
|
||||||
<PackageReference Include="System.Interactive.Async" Version="3.1.1" />
|
<PackageReference Include="System.Interactive.Async" Version="3.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' != 'Release' ">
|
||||||
|
<PackageReference Include="IDisposableAnalyzers" Version="2.0.3.3" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An image that will be uploaded to Discord.
|
/// An image that will be uploaded to Discord.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct Image
|
public struct Image : IDisposable
|
||||||
{
|
{
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the stream to be uploaded to Discord.
|
/// Gets the stream to be uploaded to Discord.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
#pragma warning disable IDISP008
|
||||||
public Stream Stream { get; }
|
public Stream Stream { get; }
|
||||||
|
#pragma warning restore IDISP008
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create the image with a <see cref="System.IO.Stream"/>.
|
/// Create the image with a <see cref="System.IO.Stream"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -19,6 +25,7 @@ namespace Discord
|
|||||||
/// </param>
|
/// </param>
|
||||||
public Image(Stream stream)
|
public Image(Stream stream)
|
||||||
{
|
{
|
||||||
|
_isDisposed = false;
|
||||||
Stream = stream;
|
Stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,15 +52,28 @@ namespace Discord
|
|||||||
/// The specified <paramref name="path"/> is invalid, (for example, it is on an unmapped drive).
|
/// The specified <paramref name="path"/> is invalid, (for example, it is on an unmapped drive).
|
||||||
/// </exception>
|
/// </exception>
|
||||||
/// <exception cref="System.UnauthorizedAccessException">
|
/// <exception cref="System.UnauthorizedAccessException">
|
||||||
/// <paramref name="path" /> specified a directory.-or- The caller does not have the required permission.
|
/// <paramref name="path" /> specified a directory.-or- The caller does not have the required permission.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
/// <exception cref="FileNotFoundException">The file specified in <paramref name="path" /> was not found.
|
/// <exception cref="FileNotFoundException">The file specified in <paramref name="path" /> was not found.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
/// <exception cref="IOException">An I/O error occurred while opening the file. </exception>
|
/// <exception cref="IOException">An I/O error occurred while opening the file. </exception>
|
||||||
public Image(string path)
|
public Image(string path)
|
||||||
{
|
{
|
||||||
|
_isDisposed = false;
|
||||||
Stream = File.OpenRead(path);
|
Stream = File.OpenRead(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
#pragma warning disable IDISP007
|
||||||
|
Stream?.Dispose();
|
||||||
|
#pragma warning restore IDISP007
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -7,7 +8,7 @@ namespace Discord.Net.Rest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a generic REST-based client.
|
/// Represents a generic REST-based client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRestClient
|
public interface IRestClient : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the HTTP header of this client for all requests.
|
/// Sets the HTTP header of this client for all requests.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord.Net.Udp
|
namespace Discord.Net.Udp
|
||||||
{
|
{
|
||||||
public interface IUdpSocket
|
public interface IUdpSocket : IDisposable
|
||||||
{
|
{
|
||||||
event Func<byte[], int, int, Task> ReceivedDatagram;
|
event Func<byte[], int, int, Task> ReceivedDatagram;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord.Net.WebSockets
|
namespace Discord.Net.WebSockets
|
||||||
{
|
{
|
||||||
public interface IWebSocketClient
|
public interface IWebSocketClient : IDisposable
|
||||||
{
|
{
|
||||||
event Func<byte[], int, int, Task> BinaryMessage;
|
event Func<byte[], int, int, Task> BinaryMessage;
|
||||||
event Func<string, Task> TextMessage;
|
event Func<string, Task> TextMessage;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Discord.Net.Providers.WS4Net
|
|||||||
private readonly SemaphoreSlim _lock;
|
private readonly SemaphoreSlim _lock;
|
||||||
private readonly Dictionary<string, string> _headers;
|
private readonly Dictionary<string, string> _headers;
|
||||||
private WS4NetSocket _client;
|
private WS4NetSocket _client;
|
||||||
|
private CancellationTokenSource _disconnectCancelTokenSource;
|
||||||
private CancellationTokenSource _cancelTokenSource;
|
private CancellationTokenSource _cancelTokenSource;
|
||||||
private CancellationToken _cancelToken, _parentToken;
|
private CancellationToken _cancelToken, _parentToken;
|
||||||
private ManualResetEventSlim _waitUntilConnect;
|
private ManualResetEventSlim _waitUntilConnect;
|
||||||
@@ -28,7 +29,7 @@ namespace Discord.Net.Providers.WS4Net
|
|||||||
{
|
{
|
||||||
_headers = new Dictionary<string, string>();
|
_headers = new Dictionary<string, string>();
|
||||||
_lock = new SemaphoreSlim(1, 1);
|
_lock = new SemaphoreSlim(1, 1);
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_disconnectCancelTokenSource = new CancellationTokenSource();
|
||||||
_cancelToken = CancellationToken.None;
|
_cancelToken = CancellationToken.None;
|
||||||
_parentToken = CancellationToken.None;
|
_parentToken = CancellationToken.None;
|
||||||
_waitUntilConnect = new ManualResetEventSlim();
|
_waitUntilConnect = new ManualResetEventSlim();
|
||||||
@@ -38,7 +39,11 @@ namespace Discord.Net.Providers.WS4Net
|
|||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
{
|
||||||
DisconnectInternalAsync(true).GetAwaiter().GetResult();
|
DisconnectInternalAsync(true).GetAwaiter().GetResult();
|
||||||
|
_lock?.Dispose();
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +68,13 @@ namespace Discord.Net.Providers.WS4Net
|
|||||||
{
|
{
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_disconnectCancelTokenSource?.Dispose();
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
_cancelTokenSource?.Dispose();
|
||||||
|
_client?.Dispose();
|
||||||
|
|
||||||
|
_disconnectCancelTokenSource = new CancellationTokenSource();
|
||||||
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _disconnectCancelTokenSource.Token);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
|
|
||||||
_client = new WS4NetSocket(host, "", customHeaderItems: _headers.ToList())
|
_client = new WS4NetSocket(host, "", customHeaderItems: _headers.ToList())
|
||||||
{
|
{
|
||||||
@@ -96,7 +106,7 @@ namespace Discord.Net.Providers.WS4Net
|
|||||||
}
|
}
|
||||||
private Task DisconnectInternalAsync(bool isDisposing = false)
|
private Task DisconnectInternalAsync(bool isDisposing = false)
|
||||||
{
|
{
|
||||||
_cancelTokenSource.Cancel();
|
_disconnectCancelTokenSource.Cancel();
|
||||||
if (_client == null)
|
if (_client == null)
|
||||||
return Task.Delay(0);
|
return Task.Delay(0);
|
||||||
|
|
||||||
@@ -125,8 +135,10 @@ namespace Discord.Net.Providers.WS4Net
|
|||||||
}
|
}
|
||||||
public void SetCancelToken(CancellationToken cancelToken)
|
public void SetCancelToken(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
_parentToken = cancelToken;
|
_parentToken = cancelToken;
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _disconnectCancelTokenSource.Token);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(byte[] data, int index, int count, bool isText)
|
public async Task SendAsync(byte[] data, int index, int count, bool isText)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Discord.Rest
|
|||||||
public ISelfUser CurrentUser { get; protected set; }
|
public ISelfUser CurrentUser { get; protected set; }
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TokenType TokenType => ApiClient.AuthTokenType;
|
public TokenType TokenType => ApiClient.AuthTokenType;
|
||||||
|
|
||||||
/// <summary> Creates a new REST-only Discord client. </summary>
|
/// <summary> Creates a new REST-only Discord client. </summary>
|
||||||
internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client)
|
internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client)
|
||||||
{
|
{
|
||||||
@@ -106,9 +106,9 @@ namespace Discord.Rest
|
|||||||
|
|
||||||
await _loggedInEvent.InvokeAsync().ConfigureAwait(false);
|
await _loggedInEvent.InvokeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
internal virtual Task OnLoginAsync(TokenType tokenType, string token)
|
internal virtual Task OnLoginAsync(TokenType tokenType, string token)
|
||||||
=> Task.Delay(0);
|
=> Task.Delay(0);
|
||||||
|
|
||||||
public async Task LogoutAsync()
|
public async Task LogoutAsync()
|
||||||
{
|
{
|
||||||
await _stateLock.WaitAsync().ConfigureAwait(false);
|
await _stateLock.WaitAsync().ConfigureAwait(false);
|
||||||
@@ -131,14 +131,17 @@ namespace Discord.Rest
|
|||||||
|
|
||||||
await _loggedOutEvent.InvokeAsync().ConfigureAwait(false);
|
await _loggedOutEvent.InvokeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
internal virtual Task OnLogoutAsync()
|
internal virtual Task OnLogoutAsync()
|
||||||
=> Task.Delay(0);
|
=> Task.Delay(0);
|
||||||
|
|
||||||
internal virtual void Dispose(bool disposing)
|
internal virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDISP007
|
||||||
ApiClient.Dispose();
|
ApiClient.Dispose();
|
||||||
|
#pragma warning restore IDISP007
|
||||||
|
_stateLock?.Dispose();
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +159,7 @@ namespace Discord.Rest
|
|||||||
ISelfUser IDiscordClient.CurrentUser => CurrentUser;
|
ISelfUser IDiscordClient.CurrentUser => CurrentUser;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
||||||
=> throw new NotSupportedException();
|
=> throw new NotSupportedException();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ namespace Discord.API
|
|||||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
|
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
|
||||||
internal void SetBaseUrl(string baseUrl)
|
internal void SetBaseUrl(string baseUrl)
|
||||||
{
|
{
|
||||||
|
RestClient?.Dispose();
|
||||||
RestClient = _restClientProvider(baseUrl);
|
RestClient = _restClientProvider(baseUrl);
|
||||||
RestClient.SetHeader("accept", "*/*");
|
RestClient.SetHeader("accept", "*/*");
|
||||||
RestClient.SetHeader("user-agent", UserAgent);
|
RestClient.SetHeader("user-agent", UserAgent);
|
||||||
@@ -93,7 +94,9 @@ namespace Discord.API
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_loginCancelToken?.Dispose();
|
_loginCancelToken?.Dispose();
|
||||||
(RestClient as IDisposable)?.Dispose();
|
RestClient?.Dispose();
|
||||||
|
RequestQueue?.Dispose();
|
||||||
|
_stateLock?.Dispose();
|
||||||
}
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
@@ -117,6 +120,7 @@ namespace Discord.API
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_loginCancelToken?.Dispose();
|
||||||
_loginCancelToken = new CancellationTokenSource();
|
_loginCancelToken = new CancellationTokenSource();
|
||||||
|
|
||||||
AuthToken = null;
|
AuthToken = null;
|
||||||
@@ -242,7 +246,7 @@ namespace Discord.API
|
|||||||
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
|
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids,
|
||||||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null)
|
||||||
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, funcName), clientBucket, options);
|
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, funcName), clientBucket, options);
|
||||||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
|
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs,
|
||||||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
|
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = options ?? new RequestOptions();
|
options = options ?? new RequestOptions();
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ namespace Discord.Rest
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
ApiClient.Dispose();
|
ApiClient.Dispose();
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -46,12 +48,12 @@ namespace Discord.Rest
|
|||||||
_applicationInfo = null;
|
_applicationInfo = null;
|
||||||
return Task.Delay(0);
|
return Task.Delay(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null)
|
public async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null)
|
||||||
{
|
{
|
||||||
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false));
|
return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<RestChannel> GetChannelAsync(ulong id, RequestOptions options = null)
|
public Task<RestChannel> GetChannelAsync(ulong id, RequestOptions options = null)
|
||||||
=> ClientHelper.GetChannelAsync(this, id, options);
|
=> ClientHelper.GetChannelAsync(this, id, options);
|
||||||
public Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(RequestOptions options = null)
|
public Task<IReadOnlyCollection<IRestPrivateChannel>> GetPrivateChannelsAsync(RequestOptions options = null)
|
||||||
@@ -60,7 +62,7 @@ namespace Discord.Rest
|
|||||||
=> ClientHelper.GetDMChannelsAsync(this, options);
|
=> ClientHelper.GetDMChannelsAsync(this, options);
|
||||||
public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null)
|
||||||
=> ClientHelper.GetGroupChannelsAsync(this, options);
|
=> ClientHelper.GetGroupChannelsAsync(this, options);
|
||||||
|
|
||||||
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null)
|
||||||
=> ClientHelper.GetConnectionsAsync(this, options);
|
=> ClientHelper.GetConnectionsAsync(this, options);
|
||||||
|
|
||||||
@@ -79,12 +81,12 @@ namespace Discord.Rest
|
|||||||
=> ClientHelper.GetGuildsAsync(this, options);
|
=> ClientHelper.GetGuildsAsync(this, options);
|
||||||
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null)
|
public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null)
|
||||||
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options);
|
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options);
|
||||||
|
|
||||||
public Task<RestUser> GetUserAsync(ulong id, RequestOptions options = null)
|
public Task<RestUser> GetUserAsync(ulong id, RequestOptions options = null)
|
||||||
=> ClientHelper.GetUserAsync(this, id, options);
|
=> ClientHelper.GetUserAsync(this, id, options);
|
||||||
public Task<RestGuildUser> GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null)
|
public Task<RestGuildUser> GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null)
|
||||||
=> ClientHelper.GetGuildUserAsync(this, guildId, id, options);
|
=> ClientHelper.GetGuildUserAsync(this, guildId, id, options);
|
||||||
|
|
||||||
public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null)
|
||||||
=> ClientHelper.GetVoiceRegionsAsync(this, options);
|
=> ClientHelper.GetVoiceRegionsAsync(this, options);
|
||||||
public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null)
|
public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null)
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ namespace Discord.Net.Converters
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var cloneStream = new MemoryStream();
|
using (var cloneStream = new MemoryStream())
|
||||||
image.Stream.CopyTo(cloneStream);
|
{
|
||||||
bytes = new byte[cloneStream.Length];
|
image.Stream.CopyTo(cloneStream);
|
||||||
cloneStream.Position = 0;
|
bytes = new byte[cloneStream.Length];
|
||||||
cloneStream.Read(bytes, 0, bytes.Length);
|
cloneStream.Position = 0;
|
||||||
length = (int)cloneStream.Length;
|
cloneStream.Read(bytes, 0, bytes.Length);
|
||||||
|
length = (int)cloneStream.Length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string base64 = Convert.ToBase64String(bytes, 0, length);
|
string base64 = Convert.ToBase64String(bytes, 0, length);
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ namespace Discord.Net.Rest
|
|||||||
{
|
{
|
||||||
_baseUrl = baseUrl;
|
_baseUrl = baseUrl;
|
||||||
|
|
||||||
|
#pragma warning disable IDISP014
|
||||||
_client = new HttpClient(new HttpClientHandler
|
_client = new HttpClient(new HttpClientHandler
|
||||||
{
|
{
|
||||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
||||||
UseCookies = false,
|
UseCookies = false,
|
||||||
UseProxy = useProxy,
|
UseProxy = useProxy,
|
||||||
});
|
});
|
||||||
|
#pragma warning restore IDISP014
|
||||||
SetHeader("accept-encoding", "gzip, deflate");
|
SetHeader("accept-encoding", "gzip, deflate");
|
||||||
|
|
||||||
_cancelToken = CancellationToken.None;
|
_cancelToken = CancellationToken.None;
|
||||||
@@ -91,12 +93,14 @@ namespace Discord.Net.Rest
|
|||||||
{
|
{
|
||||||
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
||||||
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
||||||
|
MemoryStream memoryStream = null;
|
||||||
if (multipartParams != null)
|
if (multipartParams != null)
|
||||||
{
|
{
|
||||||
foreach (var p in multipartParams)
|
foreach (var p in multipartParams)
|
||||||
{
|
{
|
||||||
switch (p.Value)
|
switch (p.Value)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDISP004
|
||||||
case string stringValue: { content.Add(new StringContent(stringValue), p.Key); continue; }
|
case string stringValue: { content.Add(new StringContent(stringValue), p.Key); continue; }
|
||||||
case byte[] byteArrayValue: { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; }
|
case byte[] byteArrayValue: { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; }
|
||||||
case Stream streamValue: { content.Add(new StreamContent(streamValue), p.Key); continue; }
|
case Stream streamValue: { content.Add(new StreamContent(streamValue), p.Key); continue; }
|
||||||
@@ -105,12 +109,15 @@ namespace Discord.Net.Rest
|
|||||||
var stream = fileValue.Stream;
|
var stream = fileValue.Stream;
|
||||||
if (!stream.CanSeek)
|
if (!stream.CanSeek)
|
||||||
{
|
{
|
||||||
var memoryStream = new MemoryStream();
|
memoryStream = new MemoryStream();
|
||||||
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||||
memoryStream.Position = 0;
|
memoryStream.Position = 0;
|
||||||
|
#pragma warning disable IDISP001
|
||||||
stream = memoryStream;
|
stream = memoryStream;
|
||||||
|
#pragma warning restore IDISP001
|
||||||
}
|
}
|
||||||
content.Add(new StreamContent(stream), p.Key, fileValue.Filename);
|
content.Add(new StreamContent(stream), p.Key, fileValue.Filename);
|
||||||
|
#pragma warning restore IDISP004
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\".");
|
default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\".");
|
||||||
@@ -118,19 +125,24 @@ namespace Discord.Net.Rest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
restRequest.Content = content;
|
restRequest.Content = content;
|
||||||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
var result = await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
||||||
|
memoryStream?.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<RestResponse> SendInternalAsync(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly)
|
private async Task<RestResponse> SendInternalAsync(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly)
|
||||||
{
|
{
|
||||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token;
|
using (var cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken))
|
||||||
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
|
{
|
||||||
|
cancelToken = cancelTokenSource.Token;
|
||||||
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
|
||||||
var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
|
|
||||||
|
|
||||||
return new RestResponse(response.StatusCode, headers, stream);
|
var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null;
|
||||||
|
|
||||||
|
return new RestResponse(response.StatusCode, headers, stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly HttpMethod Patch = new HttpMethod("PATCH");
|
private static readonly HttpMethod Patch = new HttpMethod("PATCH");
|
||||||
|
|||||||
@@ -16,23 +16,24 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
private readonly ConcurrentDictionary<string, RequestBucket> _buckets;
|
private readonly ConcurrentDictionary<string, RequestBucket> _buckets;
|
||||||
private readonly SemaphoreSlim _tokenLock;
|
private readonly SemaphoreSlim _tokenLock;
|
||||||
private readonly CancellationTokenSource _cancelToken; //Dispose token
|
private readonly CancellationTokenSource _cancelTokenSource; //Dispose token
|
||||||
private CancellationTokenSource _clearToken;
|
private CancellationTokenSource _clearToken;
|
||||||
private CancellationToken _parentToken;
|
private CancellationToken _parentToken;
|
||||||
|
private CancellationTokenSource _requestCancelTokenSource;
|
||||||
private CancellationToken _requestCancelToken; //Parent token + Clear token
|
private CancellationToken _requestCancelToken; //Parent token + Clear token
|
||||||
private DateTimeOffset _waitUntil;
|
private DateTimeOffset _waitUntil;
|
||||||
|
|
||||||
private Task _cleanupTask;
|
private Task _cleanupTask;
|
||||||
|
|
||||||
public RequestQueue()
|
public RequestQueue()
|
||||||
{
|
{
|
||||||
_tokenLock = new SemaphoreSlim(1, 1);
|
_tokenLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
_clearToken = new CancellationTokenSource();
|
_clearToken = new CancellationTokenSource();
|
||||||
_cancelToken = new CancellationTokenSource();
|
_cancelTokenSource = new CancellationTokenSource();
|
||||||
_requestCancelToken = CancellationToken.None;
|
_requestCancelToken = CancellationToken.None;
|
||||||
_parentToken = CancellationToken.None;
|
_parentToken = CancellationToken.None;
|
||||||
|
|
||||||
_buckets = new ConcurrentDictionary<string, RequestBucket>();
|
_buckets = new ConcurrentDictionary<string, RequestBucket>();
|
||||||
|
|
||||||
_cleanupTask = RunCleanup();
|
_cleanupTask = RunCleanup();
|
||||||
@@ -44,7 +45,9 @@ namespace Discord.Net.Queue
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_parentToken = cancelToken;
|
_parentToken = cancelToken;
|
||||||
_requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token).Token;
|
_requestCancelTokenSource?.Dispose();
|
||||||
|
_requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token);
|
||||||
|
_requestCancelToken = _requestCancelTokenSource.Token;
|
||||||
}
|
}
|
||||||
finally { _tokenLock.Release(); }
|
finally { _tokenLock.Release(); }
|
||||||
}
|
}
|
||||||
@@ -54,9 +57,14 @@ namespace Discord.Net.Queue
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clearToken?.Cancel();
|
_clearToken?.Cancel();
|
||||||
|
_clearToken?.Dispose();
|
||||||
_clearToken = new CancellationTokenSource();
|
_clearToken = new CancellationTokenSource();
|
||||||
if (_parentToken != null)
|
if (_parentToken != null)
|
||||||
_requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken).Token;
|
{
|
||||||
|
_requestCancelTokenSource?.Dispose();
|
||||||
|
_requestCancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken);
|
||||||
|
_requestCancelToken = _requestCancelTokenSource.Token;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
_requestCancelToken = _clearToken.Token;
|
_requestCancelToken = _clearToken.Token;
|
||||||
}
|
}
|
||||||
@@ -65,13 +73,19 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
public async Task<Stream> SendAsync(RestRequest request)
|
public async Task<Stream> SendAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
|
CancellationTokenSource createdTokenSource = null;
|
||||||
if (request.Options.CancelToken.CanBeCanceled)
|
if (request.Options.CancelToken.CanBeCanceled)
|
||||||
request.Options.CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken).Token;
|
{
|
||||||
|
createdTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken);
|
||||||
|
request.Options.CancelToken = createdTokenSource.Token;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
request.Options.CancelToken = _requestCancelToken;
|
request.Options.CancelToken = _requestCancelToken;
|
||||||
|
|
||||||
var bucket = GetOrCreateBucket(request.Options.BucketId, request);
|
var bucket = GetOrCreateBucket(request.Options.BucketId, request);
|
||||||
return await bucket.SendAsync(request).ConfigureAwait(false);
|
var result = await bucket.SendAsync(request).ConfigureAwait(false);
|
||||||
|
createdTokenSource?.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
public async Task SendAsync(WebSocketRequest request)
|
public async Task SendAsync(WebSocketRequest request)
|
||||||
{
|
{
|
||||||
@@ -109,7 +123,7 @@ namespace Discord.Net.Queue
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!_cancelToken.IsCancellationRequested)
|
while (!_cancelTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
foreach (var bucket in _buckets.Select(x => x.Value))
|
foreach (var bucket in _buckets.Select(x => x.Value))
|
||||||
@@ -117,7 +131,7 @@ namespace Discord.Net.Queue
|
|||||||
if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0)
|
if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0)
|
||||||
_buckets.TryRemove(bucket.Id, out _);
|
_buckets.TryRemove(bucket.Id, out _);
|
||||||
}
|
}
|
||||||
await Task.Delay(60000, _cancelToken.Token).ConfigureAwait(false); //Runs each minute
|
await Task.Delay(60000, _cancelTokenSource.Token).ConfigureAwait(false); //Runs each minute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
@@ -126,7 +140,10 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cancelToken.Dispose();
|
_cancelTokenSource?.Dispose();
|
||||||
|
_tokenLock?.Dispose();
|
||||||
|
_clearToken?.Dispose();
|
||||||
|
_requestCancelTokenSource?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace Discord.Audio
|
|||||||
ApiClient.ReceivedPacket += ProcessPacketAsync;
|
ApiClient.ReceivedPacket += ProcessPacketAsync;
|
||||||
|
|
||||||
_stateLock = new SemaphoreSlim(1, 1);
|
_stateLock = new SemaphoreSlim(1, 1);
|
||||||
_connection = new ConnectionManager(_stateLock, _audioLogger, 30000,
|
_connection = new ConnectionManager(_stateLock, _audioLogger, 30000,
|
||||||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
|
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
|
||||||
_connection.Connected += () => _connectedEvent.InvokeAsync();
|
_connection.Connected += () => _connectedEvent.InvokeAsync();
|
||||||
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex);
|
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex);
|
||||||
@@ -79,7 +79,7 @@ namespace Discord.Audio
|
|||||||
_keepaliveTimes = new ConcurrentQueue<KeyValuePair<ulong, int>>();
|
_keepaliveTimes = new ConcurrentQueue<KeyValuePair<ulong, int>>();
|
||||||
_ssrcMap = new ConcurrentDictionary<uint, ulong>();
|
_ssrcMap = new ConcurrentDictionary<uint, ulong>();
|
||||||
_streams = new ConcurrentDictionary<ulong, StreamPair>();
|
_streams = new ConcurrentDictionary<ulong, StreamPair>();
|
||||||
|
|
||||||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||||
_serializer.Error += (s, e) =>
|
_serializer.Error += (s, e) =>
|
||||||
{
|
{
|
||||||
@@ -91,7 +91,7 @@ namespace Discord.Audio
|
|||||||
UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false);
|
UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task StartAsync(string url, ulong userId, string sessionId, string token)
|
internal async Task StartAsync(string url, ulong userId, string sessionId, string token)
|
||||||
{
|
{
|
||||||
_url = url;
|
_url = url;
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
@@ -100,7 +100,7 @@ namespace Discord.Audio
|
|||||||
await _connection.StartAsync().ConfigureAwait(false);
|
await _connection.StartAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public async Task StopAsync()
|
public async Task StopAsync()
|
||||||
{
|
{
|
||||||
await _connection.StopAsync().ConfigureAwait(false);
|
await _connection.StopAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,11 +225,11 @@ namespace Discord.Audio
|
|||||||
|
|
||||||
if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode))
|
if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode))
|
||||||
throw new InvalidOperationException($"Discord does not support {DiscordVoiceAPIClient.Mode}");
|
throw new InvalidOperationException($"Discord does not support {DiscordVoiceAPIClient.Mode}");
|
||||||
|
|
||||||
ApiClient.SetUdpEndpoint(data.Ip, data.Port);
|
ApiClient.SetUdpEndpoint(data.Ip, data.Port);
|
||||||
await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false);
|
await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
||||||
_heartbeatTask = RunHeartbeatAsync(41250, _connection.CancelToken);
|
_heartbeatTask = RunHeartbeatAsync(41250, _connection.CancelToken);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -305,9 +305,9 @@ namespace Discord.Audio
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false);
|
await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false);
|
await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false);
|
||||||
await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false);
|
await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ namespace Discord.Audio
|
|||||||
{
|
{
|
||||||
await _audioLogger.DebugAsync("Received Keepalive").ConfigureAwait(false);
|
await _audioLogger.DebugAsync("Received Keepalive").ConfigureAwait(false);
|
||||||
|
|
||||||
ulong value =
|
ulong value =
|
||||||
((ulong)packet[0] >> 0) |
|
((ulong)packet[0] >> 0) |
|
||||||
((ulong)packet[1] >> 8) |
|
((ulong)packet[1] >> 8) |
|
||||||
((ulong)packet[2] >> 16) |
|
((ulong)packet[2] >> 16) |
|
||||||
@@ -341,7 +341,7 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc))
|
if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc))
|
||||||
{
|
{
|
||||||
await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false);
|
await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false);
|
||||||
@@ -388,7 +388,7 @@ namespace Discord.Audio
|
|||||||
var now = Environment.TickCount;
|
var now = Environment.TickCount;
|
||||||
|
|
||||||
//Did server respond to our last heartbeat?
|
//Did server respond to our last heartbeat?
|
||||||
if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis &&
|
if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis &&
|
||||||
ConnectionState == ConnectionState.Connected)
|
ConnectionState == ConnectionState.Connected)
|
||||||
{
|
{
|
||||||
_connection.Error(new Exception("Server missed last heartbeat"));
|
_connection.Error(new Exception("Server missed last heartbeat"));
|
||||||
@@ -437,7 +437,7 @@ namespace Discord.Audio
|
|||||||
{
|
{
|
||||||
await _audioLogger.WarningAsync("Failed to send keepalive", ex).ConfigureAwait(false);
|
await _audioLogger.WarningAsync("Failed to send keepalive", ex).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false);
|
await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
await _audioLogger.DebugAsync("Keepalive Stopped").ConfigureAwait(false);
|
await _audioLogger.DebugAsync("Keepalive Stopped").ConfigureAwait(false);
|
||||||
@@ -467,6 +467,7 @@ namespace Discord.Audio
|
|||||||
{
|
{
|
||||||
StopAsync().GetAwaiter().GetResult();
|
StopAsync().GetAwaiter().GetResult();
|
||||||
ApiClient.Dispose();
|
ApiClient.Dispose();
|
||||||
|
_stateLock?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Discord.Audio.Streams
|
|||||||
|
|
||||||
private readonly AudioClient _client;
|
private readonly AudioClient _client;
|
||||||
private readonly AudioStream _next;
|
private readonly AudioStream _next;
|
||||||
private readonly CancellationTokenSource _cancelTokenSource;
|
private readonly CancellationTokenSource _disposeTokenSource, _cancelTokenSource;
|
||||||
private readonly CancellationToken _cancelToken;
|
private readonly CancellationToken _cancelToken;
|
||||||
private readonly Task _task;
|
private readonly Task _task;
|
||||||
private readonly ConcurrentQueue<Frame> _queuedFrames;
|
private readonly ConcurrentQueue<Frame> _queuedFrames;
|
||||||
@@ -49,12 +49,13 @@ namespace Discord.Audio.Streams
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_queueLength = (bufferMillis + (_ticksPerFrame - 1)) / _ticksPerFrame; //Round up
|
_queueLength = (bufferMillis + (_ticksPerFrame - 1)) / _ticksPerFrame; //Round up
|
||||||
|
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_disposeTokenSource = new CancellationTokenSource();
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, cancelToken).Token;
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_disposeTokenSource.Token, cancelToken);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
_queuedFrames = new ConcurrentQueue<Frame>();
|
_queuedFrames = new ConcurrentQueue<Frame>();
|
||||||
_bufferPool = new ConcurrentQueue<byte[]>();
|
_bufferPool = new ConcurrentQueue<byte[]>();
|
||||||
for (int i = 0; i < _queueLength; i++)
|
for (int i = 0; i < _queueLength; i++)
|
||||||
_bufferPool.Enqueue(new byte[maxFrameSize]);
|
_bufferPool.Enqueue(new byte[maxFrameSize]);
|
||||||
_queueLock = new SemaphoreSlim(_queueLength, _queueLength);
|
_queueLock = new SemaphoreSlim(_queueLength, _queueLength);
|
||||||
_silenceFrames = MaxSilenceFrames;
|
_silenceFrames = MaxSilenceFrames;
|
||||||
|
|
||||||
@@ -63,7 +64,12 @@ namespace Discord.Audio.Streams
|
|||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
_cancelTokenSource.Cancel();
|
{
|
||||||
|
_disposeTokenSource?.Cancel();
|
||||||
|
_disposeTokenSource?.Dispose();
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
_queueLock?.Dispose();
|
||||||
|
}
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +137,12 @@ namespace Discord.Audio.Streams
|
|||||||
public override void WriteHeader(ushort seq, uint timestamp, bool missed) { } //Ignore, we use our own timing
|
public override void WriteHeader(ushort seq, uint timestamp, bool missed) { } //Ignore, we use our own timing
|
||||||
public override async Task WriteAsync(byte[] data, int offset, int count, CancellationToken cancelToken)
|
public override async Task WriteAsync(byte[] data, int offset, int count, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
|
CancellationTokenSource writeCancelToken = null;
|
||||||
if (cancelToken.CanBeCanceled)
|
if (cancelToken.CanBeCanceled)
|
||||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _cancelToken).Token;
|
{
|
||||||
|
writeCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _cancelToken);
|
||||||
|
cancelToken = writeCancelToken.Token;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
cancelToken = _cancelToken;
|
cancelToken = _cancelToken;
|
||||||
|
|
||||||
@@ -142,6 +152,9 @@ namespace Discord.Audio.Streams
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock
|
var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock
|
||||||
#endif
|
#endif
|
||||||
|
#pragma warning disable IDISP016
|
||||||
|
writeCancelToken?.Dispose();
|
||||||
|
#pragma warning restore IDISP016
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Buffer.BlockCopy(data, offset, buffer, 0, count);
|
Buffer.BlockCopy(data, offset, buffer, 0, count);
|
||||||
@@ -153,6 +166,7 @@ namespace Discord.Audio.Streams
|
|||||||
#endif
|
#endif
|
||||||
_isPreloaded = true;
|
_isPreloaded = true;
|
||||||
}
|
}
|
||||||
|
writeCancelToken?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task FlushAsync(CancellationToken cancelToken)
|
public override async Task FlushAsync(CancellationToken cancelToken)
|
||||||
|
|||||||
@@ -96,7 +96,17 @@ namespace Discord.Audio.Streams
|
|||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
_isDisposed = true;
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (isDisposing)
|
||||||
|
{
|
||||||
|
_signal?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using Discord.Net;
|
|||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
internal class ConnectionManager
|
internal class ConnectionManager : IDisposable
|
||||||
{
|
{
|
||||||
public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } }
|
public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } }
|
||||||
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
||||||
@@ -23,10 +23,12 @@ namespace Discord
|
|||||||
private CancellationTokenSource _combinedCancelToken, _reconnectCancelToken, _connectionCancelToken;
|
private CancellationTokenSource _combinedCancelToken, _reconnectCancelToken, _connectionCancelToken;
|
||||||
private Task _task;
|
private Task _task;
|
||||||
|
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
public ConnectionState State { get; private set; }
|
public ConnectionState State { get; private set; }
|
||||||
public CancellationToken CancelToken { get; private set; }
|
public CancellationToken CancelToken { get; private set; }
|
||||||
|
|
||||||
internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout,
|
internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout,
|
||||||
Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler)
|
Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler)
|
||||||
{
|
{
|
||||||
_stateLock = stateLock;
|
_stateLock = stateLock;
|
||||||
@@ -55,6 +57,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
await AcquireConnectionLock().ConfigureAwait(false);
|
await AcquireConnectionLock().ConfigureAwait(false);
|
||||||
var reconnectCancelToken = new CancellationTokenSource();
|
var reconnectCancelToken = new CancellationTokenSource();
|
||||||
|
_reconnectCancelToken?.Dispose();
|
||||||
_reconnectCancelToken = reconnectCancelToken;
|
_reconnectCancelToken = reconnectCancelToken;
|
||||||
_task = Task.Run(async () =>
|
_task = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -67,16 +70,16 @@ namespace Discord
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ConnectAsync(reconnectCancelToken).ConfigureAwait(false);
|
await ConnectAsync(reconnectCancelToken).ConfigureAwait(false);
|
||||||
nextReconnectDelay = 1000; //Reset delay
|
nextReconnectDelay = 1000; //Reset delay
|
||||||
await _connectionPromise.Task.ConfigureAwait(false);
|
await _connectionPromise.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
Cancel(); //In case this exception didn't come from another Error call
|
Cancel(); //In case this exception didn't come from another Error call
|
||||||
await DisconnectAsync(ex, !reconnectCancelToken.IsCancellationRequested).ConfigureAwait(false);
|
await DisconnectAsync(ex, !reconnectCancelToken.IsCancellationRequested).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Error(ex); //In case this exception didn't come from another Error call
|
Error(ex); //In case this exception didn't come from another Error call
|
||||||
if (!reconnectCancelToken.IsCancellationRequested)
|
if (!reconnectCancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -113,6 +116,8 @@ namespace Discord
|
|||||||
|
|
||||||
private async Task ConnectAsync(CancellationTokenSource reconnectCancelToken)
|
private async Task ConnectAsync(CancellationTokenSource reconnectCancelToken)
|
||||||
{
|
{
|
||||||
|
_connectionCancelToken?.Dispose();
|
||||||
|
_combinedCancelToken?.Dispose();
|
||||||
_connectionCancelToken = new CancellationTokenSource();
|
_connectionCancelToken = new CancellationTokenSource();
|
||||||
_combinedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_connectionCancelToken.Token, reconnectCancelToken.Token);
|
_combinedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_connectionCancelToken.Token, reconnectCancelToken.Token);
|
||||||
CancelToken = _combinedCancelToken.Token;
|
CancelToken = _combinedCancelToken.Token;
|
||||||
@@ -120,7 +125,7 @@ namespace Discord
|
|||||||
_connectionPromise = new TaskCompletionSource<bool>();
|
_connectionPromise = new TaskCompletionSource<bool>();
|
||||||
State = ConnectionState.Connecting;
|
State = ConnectionState.Connecting;
|
||||||
await _logger.InfoAsync("Connecting").ConfigureAwait(false);
|
await _logger.InfoAsync("Connecting").ConfigureAwait(false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var readyPromise = new TaskCompletionSource<bool>();
|
var readyPromise = new TaskCompletionSource<bool>();
|
||||||
@@ -206,5 +211,25 @@ namespace Discord
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_combinedCancelToken?.Dispose();
|
||||||
|
_reconnectCancelToken?.Dispose();
|
||||||
|
_connectionCancelToken?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ namespace Discord.WebSocket
|
|||||||
private int[] _shardIds;
|
private int[] _shardIds;
|
||||||
private DiscordSocketClient[] _shards;
|
private DiscordSocketClient[] _shards;
|
||||||
private int _totalShards;
|
private int _totalShards;
|
||||||
|
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override int Latency { get => GetLatency(); protected set { } }
|
public override int Latency { get => GetLatency(); protected set { } }
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -38,11 +40,15 @@ namespace Discord.WebSocket
|
|||||||
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
||||||
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { }
|
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { }
|
||||||
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
||||||
|
#pragma warning disable IDISP004
|
||||||
public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { }
|
public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { }
|
||||||
|
#pragma warning restore IDISP004
|
||||||
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
||||||
public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { }
|
public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { }
|
||||||
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
/// <summary> Creates a new REST/WebSocket Discord client. </summary>
|
||||||
|
#pragma warning disable IDISP004
|
||||||
public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { }
|
public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { }
|
||||||
|
#pragma warning restore IDISP004
|
||||||
private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client)
|
private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client)
|
||||||
: base(config, client)
|
: base(config, client)
|
||||||
{
|
{
|
||||||
@@ -119,10 +125,10 @@ namespace Discord.WebSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task StartAsync()
|
public override async Task StartAsync()
|
||||||
=> await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false);
|
=> await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task StopAsync()
|
public override async Task StopAsync()
|
||||||
=> await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false);
|
=> await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false);
|
||||||
|
|
||||||
public DiscordSocketClient GetShard(int id)
|
public DiscordSocketClient GetShard(int id)
|
||||||
@@ -145,7 +151,7 @@ namespace Discord.WebSocket
|
|||||||
=> await _shards[0].GetApplicationInfoAsync(options).ConfigureAwait(false);
|
=> await _shards[0].GetApplicationInfoAsync(options).ConfigureAwait(false);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override SocketGuild GetGuild(ulong id)
|
public override SocketGuild GetGuild(ulong id)
|
||||||
=> GetShardFor(id).GetGuild(id);
|
=> GetShardFor(id).GetGuild(id);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -173,7 +179,7 @@ namespace Discord.WebSocket
|
|||||||
for (int i = 0; i < _shards.Length; i++)
|
for (int i = 0; i < _shards.Length; i++)
|
||||||
result += _shards[i].PrivateChannels.Count;
|
result += _shards[i].PrivateChannels.Count;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<SocketGuild> GetGuilds()
|
private IEnumerable<SocketGuild> GetGuilds()
|
||||||
{
|
{
|
||||||
@@ -189,7 +195,7 @@ namespace Discord.WebSocket
|
|||||||
for (int i = 0; i < _shards.Length; i++)
|
for (int i = 0; i < _shards.Length; i++)
|
||||||
result += _shards[i].Guilds.Count;
|
result += _shards[i].Guilds.Count;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override SocketUser GetUser(ulong id)
|
public override SocketUser GetUser(ulong id)
|
||||||
@@ -369,5 +375,22 @@ namespace Discord.WebSocket
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
|
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
|
||||||
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id));
|
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id));
|
||||||
|
|
||||||
|
internal override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
foreach (var client in _shards)
|
||||||
|
client?.Dispose();
|
||||||
|
_connectionGroupLock?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ namespace Discord.API
|
|||||||
}
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync()
|
public async Task ConnectAsync()
|
||||||
@@ -137,6 +139,7 @@ namespace Discord.API
|
|||||||
ConnectionState = ConnectionState.Connecting;
|
ConnectionState = ConnectionState.Connecting;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_connectCancelToken?.Dispose();
|
||||||
_connectCancelToken = new CancellationTokenSource();
|
_connectCancelToken = new CancellationTokenSource();
|
||||||
if (WebSocketClient != null)
|
if (WebSocketClient != null)
|
||||||
WebSocketClient.SetCancelToken(_connectCancelToken.Token);
|
WebSocketClient.SetCancelToken(_connectCancelToken.Token);
|
||||||
@@ -209,7 +212,7 @@ namespace Discord.API
|
|||||||
await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, bytes, true, options)).ConfigureAwait(false);
|
await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, bytes, true, options)).ConfigureAwait(false);
|
||||||
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false);
|
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null)
|
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = RequestOptions.CreateOrClone(options);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ namespace Discord.WebSocket
|
|||||||
private DateTimeOffset? _statusSince;
|
private DateTimeOffset? _statusSince;
|
||||||
private RestApplication _applicationInfo;
|
private RestApplication _applicationInfo;
|
||||||
|
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
/// <summary> Gets the shard of of this client. </summary>
|
/// <summary> Gets the shard of of this client. </summary>
|
||||||
public int ShardId { get; }
|
public int ShardId { get; }
|
||||||
/// <summary> Gets the current connection state of this client. </summary>
|
/// <summary> Gets the current connection state of this client. </summary>
|
||||||
@@ -63,7 +65,7 @@ namespace Discord.WebSocket
|
|||||||
internal WebSocketProvider WebSocketProvider { get; private set; }
|
internal WebSocketProvider WebSocketProvider { get; private set; }
|
||||||
internal bool AlwaysDownloadUsers { get; private set; }
|
internal bool AlwaysDownloadUsers { get; private set; }
|
||||||
internal int? HandlerTimeout { get; private set; }
|
internal int? HandlerTimeout { get; private set; }
|
||||||
|
|
||||||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
|
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
|
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
|
||||||
@@ -110,8 +112,10 @@ namespace Discord.WebSocket
|
|||||||
/// Initializes a new REST/WebSocket-based Discord client with the provided configuration.
|
/// Initializes a new REST/WebSocket-based Discord client with the provided configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">The configuration to be used with the client.</param>
|
/// <param name="config">The configuration to be used with the client.</param>
|
||||||
|
#pragma warning disable IDISP004
|
||||||
public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) { }
|
public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) { }
|
||||||
internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) { }
|
internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) { }
|
||||||
|
#pragma warning restore IDISP004
|
||||||
private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client, SemaphoreSlim groupLock, DiscordSocketClient parentClient)
|
private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client, SemaphoreSlim groupLock, DiscordSocketClient parentClient)
|
||||||
: base(config, client)
|
: base(config, client)
|
||||||
{
|
{
|
||||||
@@ -170,11 +174,18 @@ namespace Discord.WebSocket
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
internal override void Dispose(bool disposing)
|
internal override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
StopAsync().GetAwaiter().GetResult();
|
if (disposing)
|
||||||
ApiClient.Dispose();
|
{
|
||||||
|
StopAsync().GetAwaiter().GetResult();
|
||||||
|
ApiClient?.Dispose();
|
||||||
|
_stateLock?.Dispose();
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -197,10 +208,10 @@ namespace Discord.WebSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task StartAsync()
|
public override async Task StartAsync()
|
||||||
=> await _connection.StartAsync().ConfigureAwait(false);
|
=> await _connection.StartAsync().ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task StopAsync()
|
public override async Task StopAsync()
|
||||||
=> await _connection.StopAsync().ConfigureAwait(false);
|
=> await _connection.StopAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
private async Task OnConnectingAsync()
|
private async Task OnConnectingAsync()
|
||||||
@@ -704,6 +715,7 @@ namespace Discord.WebSocket
|
|||||||
{
|
{
|
||||||
await GuildUnavailableAsync(guild).ConfigureAwait(false);
|
await GuildUnavailableAsync(guild).ConfigureAwait(false);
|
||||||
await TimedInvokeAsync(_leftGuildEvent, nameof(LeftGuild), guild).ConfigureAwait(false);
|
await TimedInvokeAsync(_leftGuildEvent, nameof(LeftGuild), guild).ConfigureAwait(false);
|
||||||
|
(guild as IDisposable).Dispose();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
internal class DiscordVoiceAPIClient
|
internal class DiscordVoiceAPIClient : IDisposable
|
||||||
{
|
{
|
||||||
public const int MaxBitrate = 128 * 1024;
|
public const int MaxBitrate = 128 * 1024;
|
||||||
public const string Mode = "xsalsa20_poly1305";
|
public const string Mode = "xsalsa20_poly1305";
|
||||||
@@ -36,7 +36,7 @@ namespace Discord.Audio
|
|||||||
private readonly AsyncEvent<Func<byte[], Task>> _receivedPacketEvent = new AsyncEvent<Func<byte[], Task>>();
|
private readonly AsyncEvent<Func<byte[], Task>> _receivedPacketEvent = new AsyncEvent<Func<byte[], Task>>();
|
||||||
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
||||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||||
|
|
||||||
private readonly JsonSerializer _serializer;
|
private readonly JsonSerializer _serializer;
|
||||||
private readonly SemaphoreSlim _connectionLock;
|
private readonly SemaphoreSlim _connectionLock;
|
||||||
private readonly IUdpSocket _udp;
|
private readonly IUdpSocket _udp;
|
||||||
@@ -103,8 +103,9 @@ namespace Discord.Audio
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_connectCancelToken?.Dispose();
|
_connectCancelToken?.Dispose();
|
||||||
(_udp as IDisposable)?.Dispose();
|
_udp?.Dispose();
|
||||||
(WebSocketClient as IDisposable)?.Dispose();
|
WebSocketClient?.Dispose();
|
||||||
|
_connectionLock?.Dispose();
|
||||||
}
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
@@ -122,7 +123,7 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
public async Task SendAsync(byte[] data, int offset, int bytes)
|
public async Task SendAsync(byte[] data, int offset, int bytes)
|
||||||
{
|
{
|
||||||
await _udp.SendAsync(data, offset, bytes).ConfigureAwait(false);
|
await _udp.SendAsync(data, offset, bytes).ConfigureAwait(false);
|
||||||
await _sentDataEvent.InvokeAsync(bytes).ConfigureAwait(false);
|
await _sentDataEvent.InvokeAsync(bytes).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +178,7 @@ namespace Discord.Audio
|
|||||||
ConnectionState = ConnectionState.Connecting;
|
ConnectionState = ConnectionState.Connecting;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_connectCancelToken?.Dispose();
|
||||||
_connectCancelToken = new CancellationTokenSource();
|
_connectCancelToken = new CancellationTokenSource();
|
||||||
var cancelToken = _connectCancelToken.Token;
|
var cancelToken = _connectCancelToken.Token;
|
||||||
|
|
||||||
@@ -208,7 +210,7 @@ namespace Discord.Audio
|
|||||||
{
|
{
|
||||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||||
ConnectionState = ConnectionState.Disconnecting;
|
ConnectionState = ConnectionState.Disconnecting;
|
||||||
|
|
||||||
try { _connectCancelToken?.Cancel(false); }
|
try { _connectCancelToken?.Cancel(false); }
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ namespace Discord.WebSocket
|
|||||||
/// Represents a WebSocket-based guild object.
|
/// Represents a WebSocket-based guild object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
public class SocketGuild : SocketEntity<ulong>, IGuild
|
public class SocketGuild : SocketEntity<ulong>, IGuild, IDisposable
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDISP002, IDISP006
|
||||||
private readonly SemaphoreSlim _audioLock;
|
private readonly SemaphoreSlim _audioLock;
|
||||||
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
|
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
|
||||||
private TaskCompletionSource<AudioClient> _audioConnectPromise;
|
private TaskCompletionSource<AudioClient> _audioConnectPromise;
|
||||||
@@ -37,6 +38,7 @@ namespace Discord.WebSocket
|
|||||||
private ImmutableArray<GuildEmote> _emotes;
|
private ImmutableArray<GuildEmote> _emotes;
|
||||||
private ImmutableArray<string> _features;
|
private ImmutableArray<string> _features;
|
||||||
private AudioClient _audioClient;
|
private AudioClient _audioClient;
|
||||||
|
#pragma warning restore IDISP002, IDISP006
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
@@ -63,7 +65,7 @@ namespace Discord.WebSocket
|
|||||||
/// number here is the most accurate in terms of counting the number of users within this guild.
|
/// number here is the most accurate in terms of counting the number of users within this guild.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Use this instead of enumerating the count of the
|
/// Use this instead of enumerating the count of the
|
||||||
/// <see cref="Discord.WebSocket.SocketGuild.Users" /> collection, as you may see discrepancy
|
/// <see cref="Discord.WebSocket.SocketGuild.Users" /> collection, as you may see discrepancy
|
||||||
/// between that and this property.
|
/// between that and this property.
|
||||||
/// </para>
|
/// </para>
|
||||||
@@ -872,9 +874,11 @@ namespace Discord.WebSocket
|
|||||||
|
|
||||||
if (external)
|
if (external)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDISP001
|
||||||
var _ = promise.TrySetResultAsync(null);
|
var _ = promise.TrySetResultAsync(null);
|
||||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
|
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
|
||||||
return null;
|
return null;
|
||||||
|
#pragma warning restore IDISP001
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_audioClient == null)
|
if (_audioClient == null)
|
||||||
@@ -897,10 +901,14 @@ namespace Discord.WebSocket
|
|||||||
};
|
};
|
||||||
audioClient.Connected += () =>
|
audioClient.Connected += () =>
|
||||||
{
|
{
|
||||||
|
#pragma warning disable IDISP001
|
||||||
var _ = promise.TrySetResultAsync(_audioClient);
|
var _ = promise.TrySetResultAsync(_audioClient);
|
||||||
|
#pragma warning restore IDISP001
|
||||||
return Task.Delay(0);
|
return Task.Delay(0);
|
||||||
};
|
};
|
||||||
|
#pragma warning disable IDISP003
|
||||||
_audioClient = audioClient;
|
_audioClient = audioClient;
|
||||||
|
#pragma warning restore IDISP003
|
||||||
}
|
}
|
||||||
|
|
||||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
|
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
|
||||||
@@ -948,6 +956,7 @@ namespace Discord.WebSocket
|
|||||||
if (_audioClient != null)
|
if (_audioClient != null)
|
||||||
await _audioClient.StopAsync().ConfigureAwait(false);
|
await _audioClient.StopAsync().ConfigureAwait(false);
|
||||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false);
|
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false);
|
||||||
|
_audioClient?.Dispose();
|
||||||
_audioClient = null;
|
_audioClient = null;
|
||||||
}
|
}
|
||||||
internal async Task FinishConnectAudio(string url, string token)
|
internal async Task FinishConnectAudio(string url, string token)
|
||||||
@@ -1130,5 +1139,12 @@ namespace Discord.WebSocket
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
||||||
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
DisconnectAudioAsync().GetAwaiter().GetResult();
|
||||||
|
_audioLock?.Dispose();
|
||||||
|
_audioClient?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,29 @@ namespace Discord.Net.Udp
|
|||||||
private readonly SemaphoreSlim _lock;
|
private readonly SemaphoreSlim _lock;
|
||||||
private UdpClient _udp;
|
private UdpClient _udp;
|
||||||
private IPEndPoint _destination;
|
private IPEndPoint _destination;
|
||||||
private CancellationTokenSource _cancelTokenSource;
|
private CancellationTokenSource _stopCancelTokenSource, _cancelTokenSource;
|
||||||
private CancellationToken _cancelToken, _parentToken;
|
private CancellationToken _cancelToken, _parentToken;
|
||||||
private Task _task;
|
private Task _task;
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
public ushort Port => (ushort)((_udp?.Client.LocalEndPoint as IPEndPoint)?.Port ?? 0);
|
public ushort Port => (ushort)((_udp?.Client.LocalEndPoint as IPEndPoint)?.Port ?? 0);
|
||||||
|
|
||||||
public DefaultUdpSocket()
|
public DefaultUdpSocket()
|
||||||
{
|
{
|
||||||
_lock = new SemaphoreSlim(1, 1);
|
_lock = new SemaphoreSlim(1, 1);
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_stopCancelTokenSource = new CancellationTokenSource();
|
||||||
}
|
}
|
||||||
private void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
{
|
||||||
StopInternalAsync(true).GetAwaiter().GetResult();
|
StopInternalAsync(true).GetAwaiter().GetResult();
|
||||||
|
_stopCancelTokenSource?.Dispose();
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
_lock?.Dispose();
|
||||||
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,9 +61,14 @@ namespace Discord.Net.Udp
|
|||||||
{
|
{
|
||||||
await StopInternalAsync().ConfigureAwait(false);
|
await StopInternalAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_stopCancelTokenSource?.Dispose();
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
_cancelTokenSource?.Dispose();
|
||||||
|
|
||||||
|
_stopCancelTokenSource = new CancellationTokenSource();
|
||||||
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _stopCancelTokenSource.Token);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
|
|
||||||
|
_udp?.Dispose();
|
||||||
_udp = new UdpClient(0);
|
_udp = new UdpClient(0);
|
||||||
|
|
||||||
_task = RunAsync(_cancelToken);
|
_task = RunAsync(_cancelToken);
|
||||||
@@ -77,7 +87,7 @@ namespace Discord.Net.Udp
|
|||||||
}
|
}
|
||||||
public async Task StopInternalAsync(bool isDisposing = false)
|
public async Task StopInternalAsync(bool isDisposing = false)
|
||||||
{
|
{
|
||||||
try { _cancelTokenSource.Cancel(false); } catch { }
|
try { _stopCancelTokenSource.Cancel(false); } catch { }
|
||||||
|
|
||||||
if (!isDisposing)
|
if (!isDisposing)
|
||||||
await (_task ?? Task.Delay(0)).ConfigureAwait(false);
|
await (_task ?? Task.Delay(0)).ConfigureAwait(false);
|
||||||
@@ -96,8 +106,11 @@ namespace Discord.Net.Udp
|
|||||||
}
|
}
|
||||||
public void SetCancelToken(CancellationToken cancelToken)
|
public void SetCancelToken(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
|
||||||
_parentToken = cancelToken;
|
_parentToken = cancelToken;
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _stopCancelTokenSource.Token);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(byte[] data, int index, int count)
|
public async Task SendAsync(byte[] data, int index, int count)
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ namespace Discord.Net.WebSockets
|
|||||||
private readonly IWebProxy _proxy;
|
private readonly IWebProxy _proxy;
|
||||||
private ClientWebSocket _client;
|
private ClientWebSocket _client;
|
||||||
private Task _task;
|
private Task _task;
|
||||||
private CancellationTokenSource _cancelTokenSource;
|
private CancellationTokenSource _disconnectTokenSource, _cancelTokenSource;
|
||||||
private CancellationToken _cancelToken, _parentToken;
|
private CancellationToken _cancelToken, _parentToken;
|
||||||
private bool _isDisposed, _isDisconnecting;
|
private bool _isDisposed, _isDisconnecting;
|
||||||
|
|
||||||
public DefaultWebSocketClient(IWebProxy proxy = null)
|
public DefaultWebSocketClient(IWebProxy proxy = null)
|
||||||
{
|
{
|
||||||
_lock = new SemaphoreSlim(1, 1);
|
_lock = new SemaphoreSlim(1, 1);
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_disconnectTokenSource = new CancellationTokenSource();
|
||||||
_cancelToken = CancellationToken.None;
|
_cancelToken = CancellationToken.None;
|
||||||
_parentToken = CancellationToken.None;
|
_parentToken = CancellationToken.None;
|
||||||
_headers = new Dictionary<string, string>();
|
_headers = new Dictionary<string, string>();
|
||||||
@@ -43,7 +43,12 @@ namespace Discord.Net.WebSockets
|
|||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
{
|
||||||
DisconnectInternalAsync(true).GetAwaiter().GetResult();
|
DisconnectInternalAsync(true).GetAwaiter().GetResult();
|
||||||
|
_disconnectTokenSource?.Dispose();
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
_lock?.Dispose();
|
||||||
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,9 +73,14 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_disconnectTokenSource?.Dispose();
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
_cancelTokenSource?.Dispose();
|
||||||
|
|
||||||
|
_disconnectTokenSource = new CancellationTokenSource();
|
||||||
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _disconnectTokenSource.Token);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
|
|
||||||
|
_client?.Dispose();
|
||||||
_client = new ClientWebSocket();
|
_client = new ClientWebSocket();
|
||||||
_client.Options.Proxy = _proxy;
|
_client.Options.Proxy = _proxy;
|
||||||
_client.Options.KeepAliveInterval = TimeSpan.Zero;
|
_client.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||||
@@ -98,7 +108,7 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
private async Task DisconnectInternalAsync(bool isDisposing = false)
|
private async Task DisconnectInternalAsync(bool isDisposing = false)
|
||||||
{
|
{
|
||||||
try { _cancelTokenSource.Cancel(false); } catch { }
|
try { _disconnectTokenSource.Cancel(false); } catch { }
|
||||||
|
|
||||||
_isDisconnecting = true;
|
_isDisconnecting = true;
|
||||||
try
|
try
|
||||||
@@ -117,7 +127,7 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
try { _client.Dispose(); }
|
try { _client.Dispose(); }
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
_client = null;
|
_client = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,8 +154,11 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
public void SetCancelToken(CancellationToken cancelToken)
|
public void SetCancelToken(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
|
||||||
_parentToken = cancelToken;
|
_parentToken = cancelToken;
|
||||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _disconnectTokenSource.Token);
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(byte[] data, int index, int count, bool isText)
|
public async Task SendAsync(byte[] data, int index, int count, bool isText)
|
||||||
@@ -166,7 +179,7 @@ namespace Discord.Net.WebSockets
|
|||||||
frameSize = count - (i * SendChunkSize);
|
frameSize = count - (i * SendChunkSize);
|
||||||
else
|
else
|
||||||
frameSize = SendChunkSize;
|
frameSize = SendChunkSize;
|
||||||
|
|
||||||
var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary;
|
var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary;
|
||||||
await _client.SendAsync(new ArraySegment<byte>(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false);
|
await _client.SendAsync(new ArraySegment<byte>(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -176,7 +189,7 @@ namespace Discord.Net.WebSockets
|
|||||||
_lock.Release();
|
_lock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunAsync(CancellationToken cancelToken)
|
private async Task RunAsync(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]);
|
var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]);
|
||||||
@@ -188,7 +201,7 @@ namespace Discord.Net.WebSockets
|
|||||||
WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false);
|
WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false);
|
||||||
byte[] result;
|
byte[] result;
|
||||||
int resultCount;
|
int resultCount;
|
||||||
|
|
||||||
if (socketResult.MessageType == WebSocketMessageType.Close)
|
if (socketResult.MessageType == WebSocketMessageType.Close)
|
||||||
throw new WebSocketClosedException((int)socketResult.CloseStatus, socketResult.CloseStatusDescription);
|
throw new WebSocketClosedException((int)socketResult.CloseStatus, socketResult.CloseStatusDescription);
|
||||||
|
|
||||||
@@ -219,7 +232,7 @@ namespace Discord.Net.WebSockets
|
|||||||
resultCount = socketResult.Count;
|
resultCount = socketResult.Count;
|
||||||
result = buffer.Array;
|
result = buffer.Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socketResult.MessageType == WebSocketMessageType.Text)
|
if (socketResult.MessageType == WebSocketMessageType.Text)
|
||||||
{
|
{
|
||||||
string text = Encoding.UTF8.GetString(result, 0, resultCount);
|
string text = Encoding.UTF8.GetString(result, 0, resultCount);
|
||||||
|
|||||||
@@ -10,4 +10,4 @@
|
|||||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
|
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
|
||||||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
|
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||||
<DebugType>portable</DebugType>
|
<DebugType>portable</DebugType>
|
||||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81</PackageTargetFallback>
|
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81</PackageTargetFallback>
|
||||||
|
<NoWarn>IDISP001,IDISP002,IDISP004,IDISP005</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="xunit.runner.json">
|
<Content Include="xunit.runner.json">
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ namespace Discord.Net
|
|||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
{
|
||||||
_blobCache.Dispose();
|
_blobCache.Dispose();
|
||||||
|
_cancelTokenSource?.Dispose();
|
||||||
|
}
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +73,7 @@ namespace Discord.Net
|
|||||||
{
|
{
|
||||||
if (method != "GET")
|
if (method != "GET")
|
||||||
throw new InvalidOperationException("This RestClient only supports GET requests.");
|
throw new InvalidOperationException("This RestClient only supports GET requests.");
|
||||||
|
|
||||||
string uri = Path.Combine(_baseUrl, endpoint);
|
string uri = Path.Combine(_baseUrl, endpoint);
|
||||||
var bytes = await _blobCache.DownloadUrl(uri, _headers);
|
var bytes = await _blobCache.DownloadUrl(uri, _headers);
|
||||||
return new RestResponse(HttpStatusCode.OK, _headers, new MemoryStream(bytes));
|
return new RestResponse(HttpStatusCode.OK, _headers, new MemoryStream(bytes));
|
||||||
@@ -84,7 +87,7 @@ namespace Discord.Net
|
|||||||
throw new InvalidOperationException("This RestClient does not support multipart requests.");
|
throw new InvalidOperationException("This RestClient does not support multipart requests.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClearAsync()
|
public async Task ClearAsync()
|
||||||
{
|
{
|
||||||
await _blobCache.InvalidateAll();
|
await _blobCache.InvalidateAll();
|
||||||
}
|
}
|
||||||
@@ -93,7 +96,7 @@ namespace Discord.Net
|
|||||||
{
|
{
|
||||||
if (Info != null)
|
if (Info != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool needsReset = false;
|
bool needsReset = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -117,4 +120,4 @@ namespace Discord.Net
|
|||||||
await _blobCache.InsertObject<CacheInfo>("info", Info);
|
await _blobCache.InsertObject<CacheInfo>("info", Info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user