Added support for .NET Standard 1.1 and 1.2
This commit is contained in:
@@ -160,8 +160,8 @@ namespace Discord.API
|
||||
LoginState = LoginState.LoggedOut;
|
||||
}
|
||||
|
||||
internal virtual Task ConnectInternalAsync() => Task.CompletedTask;
|
||||
internal virtual Task DisconnectInternalAsync() => Task.CompletedTask;
|
||||
internal virtual Task ConnectInternalAsync() => Task.Delay(0);
|
||||
internal virtual Task DisconnectInternalAsync() => Task.Delay(0);
|
||||
|
||||
//Core
|
||||
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids,
|
||||
|
||||
@@ -1,60 +1,30 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
|
||||
<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Description>A .Net API wrapper and bot framework for Discord.</Description>
|
||||
<VersionPrefix>1.0.0-beta2</VersionPrefix>
|
||||
<TargetFramework>netstandard1.3</TargetFramework>
|
||||
<TargetFrameworks>netstandard1.1;netstandard1.3</TargetFrameworks>
|
||||
<AssemblyName>Discord.Net.Core</AssemblyName>
|
||||
<PackageTags>discord;discordapp</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
|
||||
<PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>git://github.com/RogueException/Discord.Net</RepositoryUrl>
|
||||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8</PackageTargetFallback>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="**\*.cs" />
|
||||
<EmbeddedResource Include="**\*.resx" />
|
||||
<EmbeddedResource Include="compiler\resources\**\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk">
|
||||
<Version>1.0.0-alpha-20161104-2</Version>
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Win32.Primitives">
|
||||
<Version>4.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>9.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Collections.Concurrent">
|
||||
<Version>4.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Collections.Immutable">
|
||||
<Version>1.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Interactive.Async">
|
||||
<Version>3.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Net.Http">
|
||||
<Version>4.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Net.WebSockets.Client">
|
||||
<Version>4.3.0</Version>
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="1.3.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="3.1.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.0" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<SignAssembly>False</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
|
||||
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
|
||||
<WarningsAsErrors>true</WarningsAsErrors>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -9,8 +9,10 @@ namespace Discord
|
||||
{
|
||||
/// <summary> Sends a message to this message channel. </summary>
|
||||
Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null);
|
||||
#if NETSTANDARD1_3
|
||||
/// <summary> Sends a file to this text channel, with an optional caption. </summary>
|
||||
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null);
|
||||
#endif
|
||||
/// <summary> Sends a file to this text channel, with an optional caption. </summary>
|
||||
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, RequestOptions options = null);
|
||||
|
||||
|
||||
@@ -8,24 +8,30 @@ namespace Discord
|
||||
{
|
||||
internal static class CollectionExtensions
|
||||
{
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source)
|
||||
=> new ConcurrentDictionaryWrapper<TValue>(source.Select(x => x.Value), () => source.Count);
|
||||
//public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this IReadOnlyCollection<TValue> source)
|
||||
// => new CollectionWrapper<TValue>(source, () => source.Count);
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this ICollection<TValue> source)
|
||||
=> new CollectionWrapper<TValue>(source, () => source.Count);
|
||||
//public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source)
|
||||
// => new CollectionWrapper<TValue>(source.Select(x => x.Value), () => source.Count);
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IDictionary<TKey, TValue> source)
|
||||
=> new CollectionWrapper<TValue>(source.Select(x => x.Value), () => source.Count);
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source)
|
||||
=> new ConcurrentDictionaryWrapper<TValue>(query, () => source.Count);
|
||||
=> new CollectionWrapper<TValue>(query, () => source.Count);
|
||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this IEnumerable<TValue> query, Func<int> countFunc)
|
||||
=> new ConcurrentDictionaryWrapper<TValue>(query, countFunc);
|
||||
=> new CollectionWrapper<TValue>(query, countFunc);
|
||||
}
|
||||
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal struct ConcurrentDictionaryWrapper<TValue> : IReadOnlyCollection<TValue>
|
||||
internal struct CollectionWrapper<TValue> : IReadOnlyCollection<TValue>
|
||||
{
|
||||
private readonly IEnumerable<TValue> _query;
|
||||
private readonly Func<int> _countFunc;
|
||||
|
||||
//It's okay that this count is affected by race conditions - we're wrapping a concurrent collection and that's to be expected
|
||||
public int Count => _countFunc();
|
||||
|
||||
public ConcurrentDictionaryWrapper(IEnumerable<TValue> query, Func<int> countFunc)
|
||||
|
||||
public CollectionWrapper(IEnumerable<TValue> query, Func<int> countFunc)
|
||||
{
|
||||
_query = query;
|
||||
_countFunc = countFunc;
|
||||
|
||||
@@ -17,56 +17,68 @@ namespace Discord.Logging
|
||||
ClientLogger = new Logger(this, "Discord");
|
||||
}
|
||||
|
||||
public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null)
|
||||
{
|
||||
if (severity <= Level)
|
||||
await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false);
|
||||
}
|
||||
public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null)
|
||||
{
|
||||
if (severity <= Level)
|
||||
await _messageEvent.InvokeAsync(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false);
|
||||
}
|
||||
public async Task LogAsync(LogSeverity severity, string source, Exception ex)
|
||||
{
|
||||
if (severity <= Level)
|
||||
await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false);
|
||||
}
|
||||
public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null)
|
||||
{
|
||||
if (severity <= Level)
|
||||
await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false);
|
||||
}
|
||||
#if NETSTANDARD1_3
|
||||
public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null)
|
||||
{
|
||||
if (severity <= Level)
|
||||
await _messageEvent.InvokeAsync(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
public Task ErrorAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Error, source, message, ex);
|
||||
public Task ErrorAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Error, source, message, ex);
|
||||
public Task ErrorAsync(string source, Exception ex)
|
||||
=> LogAsync(LogSeverity.Error, source, ex);
|
||||
public Task ErrorAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Error, source, message, ex);
|
||||
#if NETSTANDARD1_3
|
||||
public Task ErrorAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Error, source, message, ex);
|
||||
#endif
|
||||
|
||||
public Task WarningAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Warning, source, message, ex);
|
||||
public Task WarningAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Warning, source, message, ex);
|
||||
public Task WarningAsync(string source, Exception ex)
|
||||
=> LogAsync(LogSeverity.Warning, source, ex);
|
||||
public Task WarningAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Warning, source, message, ex);
|
||||
#if NETSTANDARD1_3
|
||||
public Task WarningAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Warning, source, message, ex);
|
||||
#endif
|
||||
|
||||
public Task InfoAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Info, source, message, ex);
|
||||
public Task InfoAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Info, source, message, ex);
|
||||
public Task InfoAsync(string source, Exception ex)
|
||||
=> LogAsync(LogSeverity.Info, source, ex);
|
||||
public Task InfoAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Info, source, message, ex);
|
||||
#if NETSTANDARD1_3
|
||||
public Task InfoAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Info, source, message, ex);
|
||||
#endif
|
||||
|
||||
public Task VerboseAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Verbose, source, message, ex);
|
||||
public Task VerboseAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Verbose, source, message, ex);
|
||||
public Task VerboseAsync(string source, Exception ex)
|
||||
=> LogAsync(LogSeverity.Verbose, source, ex);
|
||||
public Task VerboseAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Verbose, source, message, ex);
|
||||
#if NETSTANDARD1_3
|
||||
public Task VerboseAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Verbose, source, message, ex);
|
||||
#endif
|
||||
|
||||
public Task DebugAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Debug, source, message, ex);
|
||||
public Task DebugAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Debug, source, message, ex);
|
||||
public Task DebugAsync(string source, Exception ex)
|
||||
=> LogAsync(LogSeverity.Debug, source, ex);
|
||||
public Task DebugAsync(string source, string message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Debug, source, message, ex);
|
||||
#if NETSTANDARD1_3
|
||||
public Task DebugAsync(string source, FormattableString message, Exception ex = null)
|
||||
=> LogAsync(LogSeverity.Debug, source, message, ex);
|
||||
#endif
|
||||
|
||||
public Logger CreateLogger(string name) => new Logger(this, name);
|
||||
|
||||
|
||||
@@ -20,42 +20,54 @@ namespace Discord.Logging
|
||||
=> _manager.LogAsync(severity, Name, exception);
|
||||
public Task LogAsync(LogSeverity severity, string message, Exception exception = null)
|
||||
=> _manager.LogAsync(severity, Name, message, exception);
|
||||
#if NETSTANDARD1_3
|
||||
public Task LogAsync(LogSeverity severity, FormattableString message, Exception exception = null)
|
||||
=> _manager.LogAsync(severity, Name, message, exception);
|
||||
#endif
|
||||
|
||||
public Task ErrorAsync(string message, Exception exception = null)
|
||||
=> _manager.ErrorAsync(Name, message, exception);
|
||||
public Task ErrorAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.ErrorAsync(Name, message, exception);
|
||||
public Task ErrorAsync(Exception exception)
|
||||
=> _manager.ErrorAsync(Name, exception);
|
||||
public Task ErrorAsync(string message, Exception exception = null)
|
||||
=> _manager.ErrorAsync(Name, message, exception);
|
||||
#if NETSTANDARD1_3
|
||||
public Task ErrorAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.ErrorAsync(Name, message, exception);
|
||||
#endif
|
||||
|
||||
public Task WarningAsync(string message, Exception exception = null)
|
||||
=> _manager.WarningAsync(Name, message, exception);
|
||||
public Task WarningAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.WarningAsync(Name, message, exception);
|
||||
public Task WarningAsync(Exception exception)
|
||||
=> _manager.WarningAsync(Name, exception);
|
||||
public Task WarningAsync(string message, Exception exception = null)
|
||||
=> _manager.WarningAsync(Name, message, exception);
|
||||
#if NETSTANDARD1_3
|
||||
public Task WarningAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.WarningAsync(Name, message, exception);
|
||||
#endif
|
||||
|
||||
public Task InfoAsync(string message, Exception exception = null)
|
||||
=> _manager.InfoAsync(Name, message, exception);
|
||||
public Task InfoAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.InfoAsync(Name, message, exception);
|
||||
public Task InfoAsync(Exception exception)
|
||||
=> _manager.InfoAsync(Name, exception);
|
||||
public Task InfoAsync(string message, Exception exception = null)
|
||||
=> _manager.InfoAsync(Name, message, exception);
|
||||
#if NETSTANDARD1_3
|
||||
public Task InfoAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.InfoAsync(Name, message, exception);
|
||||
#endif
|
||||
|
||||
public Task VerboseAsync(string message, Exception exception = null)
|
||||
=> _manager.VerboseAsync(Name, message, exception);
|
||||
public Task VerboseAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.VerboseAsync(Name, message, exception);
|
||||
public Task VerboseAsync(Exception exception)
|
||||
=> _manager.VerboseAsync(Name, exception);
|
||||
public Task VerboseAsync(string message, Exception exception = null)
|
||||
=> _manager.VerboseAsync(Name, message, exception);
|
||||
#if NETSTANDARD1_3
|
||||
public Task VerboseAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.VerboseAsync(Name, message, exception);
|
||||
#endif
|
||||
|
||||
public Task DebugAsync(string message, Exception exception = null)
|
||||
=> _manager.DebugAsync(Name, message, exception);
|
||||
public Task DebugAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.DebugAsync(Name, message, exception);
|
||||
public Task DebugAsync(Exception exception)
|
||||
=> _manager.DebugAsync(Name, exception);
|
||||
public Task DebugAsync(string message, Exception exception = null)
|
||||
=> _manager.DebugAsync(Name, message, exception);
|
||||
#if NETSTANDARD1_3
|
||||
public Task DebugAsync(FormattableString message, Exception exception = null)
|
||||
=> _manager.DebugAsync(Name, message, exception);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Discord.Net.Queue
|
||||
#endif
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var now = DateTimeUtils.ToUnixSeconds(DateTimeOffset.UtcNow);
|
||||
DateTimeOffset? resetTick = null;
|
||||
|
||||
//Using X-RateLimit-Remaining causes a race condition
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Discord.Net
|
||||
IsGlobal = headers.TryGetValue("X-RateLimit-Global", out temp) ? bool.Parse(temp) : false;
|
||||
Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) ? int.Parse(temp) : (int?)null;
|
||||
Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) ? int.Parse(temp) : (int?)null;
|
||||
Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) ? DateTimeOffset.FromUnixTimeSeconds(int.Parse(temp)) : (DateTimeOffset?)null;
|
||||
Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) ? DateTimeUtils.FromUnixSeconds(int.Parse(temp)) : (DateTimeOffset?)null;
|
||||
RetryAfter = headers.TryGetValue("Retry-After", out temp) ? int.Parse(temp) : (int?)null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Net.Rest
|
||||
{
|
||||
public sealed class DefaultRestClient : IRestClient
|
||||
{
|
||||
private const int HR_SECURECHANNELFAILED = -2146233079;
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly string _baseUrl;
|
||||
private readonly JsonSerializer _errorDeserializer;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private CancellationToken _cancelToken, _parentToken;
|
||||
private bool _isDisposed;
|
||||
|
||||
public DefaultRestClient(string baseUrl)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
||||
UseCookies = false,
|
||||
UseProxy = false
|
||||
});
|
||||
SetHeader("accept-encoding", "gzip, deflate");
|
||||
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_cancelToken = CancellationToken.None;
|
||||
_parentToken = CancellationToken.None;
|
||||
_errorDeserializer = new JsonSerializer();
|
||||
}
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
_client.Dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public void SetHeader(string key, string value)
|
||||
{
|
||||
_client.DefaultRequestHeaders.Remove(key);
|
||||
if (value != null)
|
||||
_client.DefaultRequestHeaders.Add(key, value);
|
||||
}
|
||||
public void SetCancelToken(CancellationToken cancelToken)
|
||||
{
|
||||
_parentToken = cancelToken;
|
||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
||||
}
|
||||
|
||||
public async Task<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly)
|
||||
{
|
||||
string uri = Path.Combine(_baseUrl, endpoint);
|
||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
||||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly)
|
||||
{
|
||||
string uri = Path.Combine(_baseUrl, endpoint);
|
||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
||||
{
|
||||
restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly)
|
||||
{
|
||||
string uri = Path.Combine(_baseUrl, endpoint);
|
||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
||||
{
|
||||
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
||||
if (multipartParams != null)
|
||||
{
|
||||
foreach (var p in multipartParams)
|
||||
{
|
||||
//TODO: C#7 Typeswitch candidate
|
||||
var stringValue = p.Value as string;
|
||||
if (stringValue != null) { content.Add(new StringContent(stringValue), p.Key); continue; }
|
||||
var byteArrayValue = p.Value as byte[];
|
||||
if (byteArrayValue != null) { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; }
|
||||
var streamValue = p.Value as Stream;
|
||||
if (streamValue != null) { content.Add(new StreamContent(streamValue), p.Key); continue; }
|
||||
if (p.Value is MultipartFile)
|
||||
{
|
||||
var fileValue = (MultipartFile)p.Value;
|
||||
content.Add(new StreamContent(fileValue.Stream), p.Key, fileValue.Filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"");
|
||||
}
|
||||
}
|
||||
restRequest.Content = content;
|
||||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<RestResponse> SendInternalAsync(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token;
|
||||
HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
|
||||
|
||||
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 HttpMethod GetMethod(string method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case "DELETE": return HttpMethod.Delete;
|
||||
case "GET": return HttpMethod.Get;
|
||||
case "PATCH": return _patch;
|
||||
case "POST": return HttpMethod.Post;
|
||||
case "PUT": return HttpMethod.Put;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Discord.Net.Core/Net/Udp/IUdpSocket.cs
Normal file
19
src/Discord.Net.Core/Net/Udp/IUdpSocket.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Net.Udp
|
||||
{
|
||||
public interface IUdpSocket
|
||||
{
|
||||
event Func<byte[], int, int, Task> ReceivedDatagram;
|
||||
|
||||
void SetCancelToken(CancellationToken cancelToken);
|
||||
void SetDestination(string host, int port);
|
||||
|
||||
Task StartAsync();
|
||||
Task StopAsync();
|
||||
|
||||
Task SendAsync(byte[] data, int index, int count);
|
||||
}
|
||||
}
|
||||
4
src/Discord.Net.Core/Net/Udp/UdpSocketProvider.cs
Normal file
4
src/Discord.Net.Core/Net/Udp/UdpSocketProvider.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace Discord.Net.Udp
|
||||
{
|
||||
public delegate IUdpSocket UdpSocketProvider();
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Net.WebSockets
|
||||
{
|
||||
public class DefaultWebSocketClient : IWebSocketClient
|
||||
{
|
||||
public const int ReceiveChunkSize = 16 * 1024; //16KB
|
||||
public const int SendChunkSize = 4 * 1024; //4KB
|
||||
private const int HR_TIMEOUT = -2147012894;
|
||||
|
||||
public event Func<byte[], int, int, Task> BinaryMessage;
|
||||
public event Func<string, Task> TextMessage;
|
||||
public event Func<Exception, Task> Closed;
|
||||
|
||||
private readonly SemaphoreSlim _sendLock;
|
||||
private readonly Dictionary<string, string> _headers;
|
||||
private ClientWebSocket _client;
|
||||
private Task _task;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private CancellationToken _cancelToken, _parentToken;
|
||||
private bool _isDisposed;
|
||||
|
||||
public DefaultWebSocketClient()
|
||||
{
|
||||
_sendLock = new SemaphoreSlim(1, 1);
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_cancelToken = CancellationToken.None;
|
||||
_parentToken = CancellationToken.None;
|
||||
_headers = new Dictionary<string, string>();
|
||||
}
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
_client.Dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(string host)
|
||||
{
|
||||
await _sendLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ConnectInternalAsync(host).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
private async Task ConnectInternalAsync(string host)
|
||||
{
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
||||
|
||||
_client = new ClientWebSocket();
|
||||
_client.Options.Proxy = null;
|
||||
_client.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||
foreach (var header in _headers)
|
||||
{
|
||||
if (header.Value != null)
|
||||
_client.Options.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
await _client.ConnectAsync(new Uri(host), _cancelToken).ConfigureAwait(false);
|
||||
_task = RunAsync(_cancelToken);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
await _sendLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
private async Task DisconnectInternalAsync()
|
||||
{
|
||||
try { _cancelTokenSource.Cancel(false); } catch { }
|
||||
|
||||
await (_task ?? Task.CompletedTask).ConfigureAwait(false);
|
||||
|
||||
if (_client != null && _client.State == WebSocketState.Open)
|
||||
{
|
||||
var token = new CancellationToken();
|
||||
await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", token);
|
||||
_client.Dispose();
|
||||
_client = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHeader(string key, string value)
|
||||
{
|
||||
_headers[key] = value;
|
||||
}
|
||||
public void SetCancelToken(CancellationToken cancelToken)
|
||||
{
|
||||
_parentToken = cancelToken;
|
||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
|
||||
}
|
||||
|
||||
public async Task SendAsync(byte[] data, int index, int count, bool isText)
|
||||
{
|
||||
await _sendLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_client == null) return;
|
||||
|
||||
int frameCount = (int)Math.Ceiling((double)count / SendChunkSize);
|
||||
|
||||
for (int i = 0; i < frameCount; i++, index += SendChunkSize)
|
||||
{
|
||||
bool isLast = i == (frameCount - 1);
|
||||
|
||||
int frameSize;
|
||||
if (isLast)
|
||||
frameSize = count - (i * SendChunkSize);
|
||||
else
|
||||
frameSize = SendChunkSize;
|
||||
|
||||
var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary;
|
||||
await _client.SendAsync(new ArraySegment<byte>(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunAsync(CancellationToken cancelToken)
|
||||
{
|
||||
var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]);
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false);
|
||||
byte[] result;
|
||||
int resultCount;
|
||||
|
||||
if (socketResult.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
var _ = Closed(new WebSocketClosedException((int)socketResult.CloseStatus, socketResult.CloseStatusDescription));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!socketResult.EndOfMessage)
|
||||
{
|
||||
//This is a large message (likely just READY), lets create a temporary expandable stream
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
stream.Write(buffer.Array, 0, socketResult.Count);
|
||||
do
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested) return;
|
||||
socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false);
|
||||
stream.Write(buffer.Array, 0, socketResult.Count);
|
||||
}
|
||||
while (socketResult == null || !socketResult.EndOfMessage);
|
||||
|
||||
//Use the internal buffer if we can get it
|
||||
resultCount = (int)stream.Length;
|
||||
ArraySegment<byte> streamBuffer;
|
||||
if (stream.TryGetBuffer(out streamBuffer))
|
||||
result = streamBuffer.Array;
|
||||
else
|
||||
result = stream.ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Small message
|
||||
resultCount = socketResult.Count;
|
||||
result = buffer.Array;
|
||||
}
|
||||
|
||||
if (socketResult.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
string text = Encoding.UTF8.GetString(result, 0, resultCount);
|
||||
await TextMessage(text).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await BinaryMessage(result, 0, resultCount).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT)
|
||||
{
|
||||
var _ = Closed(new Exception("Connection timed out.", ex));
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
//This cannot be awaited otherwise we'll deadlock when DiscordApiClient waits for this task to complete.
|
||||
var _ = Closed(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,58 @@
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal static class DateTimeUtils
|
||||
{
|
||||
public static DateTimeOffset FromSnowflake(ulong value)
|
||||
=> DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL));
|
||||
internal static class DateTimeUtils
|
||||
{
|
||||
#if !NETSTANDARD1_3
|
||||
//https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/DateTimeOffset.cs
|
||||
private const long UnixEpochTicks = 621355968000000000;
|
||||
private const long UnixEpochSeconds = 62135596800;
|
||||
#endif
|
||||
|
||||
public static DateTimeOffset FromTicks(long ticks)
|
||||
=> new DateTimeOffset(ticks, TimeSpan.Zero);
|
||||
public static DateTimeOffset? FromTicks(long? ticks)
|
||||
=> ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null;
|
||||
}
|
||||
public static DateTimeOffset FromSnowflake(ulong value)
|
||||
=> FromUnixMilliseconds((long)((value >> 22) + 1420070400000UL));
|
||||
|
||||
public static DateTimeOffset FromTicks(long ticks)
|
||||
=> new DateTimeOffset(ticks, TimeSpan.Zero);
|
||||
public static DateTimeOffset? FromTicks(long? ticks)
|
||||
=> ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null;
|
||||
|
||||
public static DateTimeOffset FromUnixSeconds(long seconds)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
return DateTimeOffset.FromUnixTimeSeconds(seconds);
|
||||
#else
|
||||
long ticks = seconds * TimeSpan.TicksPerSecond + UnixEpochTicks;
|
||||
return new DateTimeOffset(ticks, TimeSpan.Zero);
|
||||
#endif
|
||||
}
|
||||
public static DateTimeOffset FromUnixMilliseconds(long seconds)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(seconds);
|
||||
#else
|
||||
long ticks = seconds * TimeSpan.TicksPerMillisecond + UnixEpochTicks;
|
||||
return new DateTimeOffset(ticks, TimeSpan.Zero);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static long ToUnixSeconds(DateTimeOffset dto)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
return dto.ToUnixTimeSeconds();
|
||||
#else
|
||||
long seconds = dto.UtcDateTime.Ticks / TimeSpan.TicksPerSecond;
|
||||
return seconds - UnixEpochSeconds;
|
||||
#endif
|
||||
}
|
||||
public static long ToUnixMilliseconds(DateTimeOffset dto)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
return dto.ToUnixTimeMilliseconds();
|
||||
#else
|
||||
long seconds = dto.UtcDateTime.Ticks / TimeSpan.TicksPerMillisecond;
|
||||
return seconds - UnixEpochSeconds;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,25 +26,16 @@
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Primitives": "4.3.0",
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"System.Collections.Concurrent": "4.3.0",
|
||||
"System.Collections.Immutable": "1.3.0",
|
||||
"System.Interactive.Async": "3.1.0",
|
||||
"System.Net.Http": "4.3.0",
|
||||
"System.Net.WebSockets.Client": {
|
||||
"version": "4.3.0",
|
||||
"type": "build"
|
||||
}
|
||||
"System.Runtime.Serialization.Primitives": "4.1.1"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"netstandard1.3": {
|
||||
"imports": [
|
||||
"dotnet5.4",
|
||||
"dnxcore50",
|
||||
"portable-net45+win8"
|
||||
]
|
||||
}
|
||||
"netstandard1.1": {},
|
||||
"netstandard1.3": {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user