mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-10 01:16:24 +00:00
Merge branch 'master' of https://github.com/JKorf/CryptoExchange.Net
This commit is contained in:
commit
e035a34316
@ -173,12 +173,15 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
TestImplementation client;
|
TestImplementation client;
|
||||||
if (withOptions)
|
if (withOptions)
|
||||||
{
|
{
|
||||||
client = new TestImplementation(new ExchangeOptions()
|
var options = new ExchangeOptions()
|
||||||
{
|
{
|
||||||
ApiCredentials = new ApiCredentials("Test", "Test2"),
|
ApiCredentials = new ApiCredentials("Test", "Test2"),
|
||||||
LogVerbosity = verbosity,
|
LogVerbosity = verbosity
|
||||||
LogWriters = new List<TextWriter>() { tw }
|
};
|
||||||
});
|
if (tw != null)
|
||||||
|
options.LogWriters = new List<TextWriter>() { tw };
|
||||||
|
|
||||||
|
client = new TestImplementation(options);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Attributes
|
||||||
|
{
|
||||||
|
public class JsonOptionalPropertyAttribute : Attribute
|
||||||
|
{
|
||||||
|
public JsonOptionalPropertyAttribute() { }
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,88 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Authentication
|
namespace CryptoExchange.Net.Authentication
|
||||||
{
|
{
|
||||||
public class ApiCredentials
|
public class ApiCredentials: IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The api key
|
/// The api key to authenticate requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; }
|
public SecureString Key { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The api secret
|
/// The api secret to authenticate requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Secret { get; }
|
public SecureString Secret { get; }
|
||||||
|
|
||||||
public ApiCredentials() { }
|
/// <summary>
|
||||||
|
/// The private key to authenticate requests
|
||||||
|
/// </summary>
|
||||||
|
public SecureString PrivateKey { get; }
|
||||||
|
|
||||||
public ApiCredentials(string key, string secret)
|
/// <summary>
|
||||||
|
/// Create Api credentials providing a private key for authenication
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="privateKey">The private key used for signing</param>
|
||||||
|
public ApiCredentials(SecureString privateKey)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
|
PrivateKey = privateKey;
|
||||||
throw new ArgumentException("Apikey or apisecret not provided");
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create Api credentials providing a private key for authenication
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="privateKey">The private key used for signing</param>
|
||||||
|
public ApiCredentials(string privateKey)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrEmpty(privateKey))
|
||||||
|
throw new ArgumentException("Private key can't be null/empty");
|
||||||
|
|
||||||
|
var securePrivateKey = new SecureString();
|
||||||
|
foreach (var c in privateKey)
|
||||||
|
securePrivateKey.AppendChar(c);
|
||||||
|
securePrivateKey.MakeReadOnly();
|
||||||
|
PrivateKey = securePrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create Api credentials providing a api key and secret for authenciation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The api key used for identification</param>
|
||||||
|
/// <param name="secret">The api secret used for signing</param>
|
||||||
|
public ApiCredentials(SecureString key, SecureString secret)
|
||||||
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
Secret = secret;
|
Secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create Api credentials providing a private key for authenication
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="privateKey">The private key used for signing</param>
|
||||||
|
public ApiCredentials(string key, string secret)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
|
||||||
|
throw new ArgumentException("Key and secret can't be null/empty");
|
||||||
|
|
||||||
|
var secureApiKey = new SecureString();
|
||||||
|
foreach (var c in key)
|
||||||
|
secureApiKey.AppendChar(c);
|
||||||
|
secureApiKey.MakeReadOnly();
|
||||||
|
Key = secureApiKey;
|
||||||
|
|
||||||
|
var secureApiSecret = new SecureString();
|
||||||
|
foreach (var c in secret)
|
||||||
|
secureApiSecret.AppendChar(c);
|
||||||
|
secureApiSecret.MakeReadOnly();
|
||||||
|
Secret = secureApiSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Key?.Dispose();
|
||||||
|
Secret?.Dispose();
|
||||||
|
PrivateKey?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,25 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
Credentials = credentials;
|
Credentials = credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract string AddAuthenticationToUriString(string uri, bool signed);
|
public virtual string AddAuthenticationToUriString(string uri, bool signed)
|
||||||
public abstract IRequest AddAuthenticationToRequest(IRequest request, bool signed);
|
{
|
||||||
public abstract string Sign(string toSign);
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IRequest AddAuthenticationToRequest(IRequest request, bool signed)
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string Sign(string toSign)
|
||||||
|
{
|
||||||
|
return toSign;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual byte[] Sign(byte[] toSign)
|
||||||
|
{
|
||||||
|
return toSign;
|
||||||
|
}
|
||||||
|
|
||||||
protected string ByteToString(byte[] buff)
|
protected string ByteToString(byte[] buff)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@ -41,15 +43,37 @@ namespace CryptoExchange.Net.Converters
|
|||||||
if (((JToken)value).Type == JTokenType.Null)
|
if (((JToken)value).Type == JTokenType.Null)
|
||||||
value = null;
|
value = null;
|
||||||
|
|
||||||
|
if ((property.PropertyType == typeof(decimal)
|
||||||
|
|| property.PropertyType == typeof(decimal?))
|
||||||
|
&& value.ToString().Contains("e"))
|
||||||
|
{
|
||||||
|
if (decimal.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var dec))
|
||||||
|
property.SetValue(result, dec);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
property.SetValue(result, value == null ? null : Convert.ChangeType(value, property.PropertyType));
|
property.SetValue(result, value == null ? null : Convert.ChangeType(value, property.PropertyType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
writer.WriteStartArray();
|
||||||
|
var props = value.GetType().GetProperties();
|
||||||
|
var ordered = props.OrderBy(p => p.GetCustomAttribute<ArrayPropertyAttribute>()?.Index);
|
||||||
|
|
||||||
|
foreach (var prop in ordered)
|
||||||
|
{
|
||||||
|
var converterAttribute = (JsonConverterAttribute)prop.GetCustomAttribute(typeof(JsonConverterAttribute));
|
||||||
|
if(converterAttribute != null)
|
||||||
|
writer.WriteValue(JsonConvert.SerializeObject(prop.GetValue(value), (JsonConverter)Activator.CreateInstance(converterAttribute.ConverterType)));
|
||||||
|
else
|
||||||
|
writer.WriteValue(JsonConvert.SerializeObject(prop.GetValue(value)));
|
||||||
|
}
|
||||||
|
writer.WriteEndArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@ -25,20 +26,30 @@ namespace CryptoExchange.Net.Converters
|
|||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var val = Mapping.SingleOrDefault(v => v.Value == reader.Value.ToString()).Key;
|
if (reader.Value == null)
|
||||||
if (val != null)
|
return null;
|
||||||
return val;
|
|
||||||
return Mapping.Single(v => v.Value.ToLower() == reader.Value.ToString().ToLower()).Key;
|
var value = reader.Value.ToString();
|
||||||
|
if (Mapping.ContainsValue(value))
|
||||||
|
return Mapping.Single(m => m.Value == value).Key;
|
||||||
|
|
||||||
|
var lowerResult = Mapping.SingleOrDefault(m => m.Value.ToLower() == value.ToLower());
|
||||||
|
if (!lowerResult.Equals(default(KeyValuePair<T, string>)))
|
||||||
|
return lowerResult.Key;
|
||||||
|
|
||||||
|
Debug.WriteLine($"Cannot map enum. Type: {typeof(T)}, Value: {value}");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T ReadString(string data)
|
public T ReadString(string data)
|
||||||
{
|
{
|
||||||
return Mapping.Single(v => v.Value == data).Key;
|
return Mapping.SingleOrDefault(v => v.Value == data).Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
return objectType == typeof(T);
|
// Check if it is type, or nullable of type
|
||||||
|
return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ namespace CryptoExchange.Net.Converters
|
|||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
|
if (reader.Value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
var t = long.Parse(reader.Value.ToString());
|
var t = long.Parse(reader.Value.ToString());
|
||||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(t);
|
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(t);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Globalization;
|
||||||
using System.Text;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Converters
|
namespace CryptoExchange.Net.Converters
|
||||||
@ -14,8 +13,11 @@ namespace CryptoExchange.Net.Converters
|
|||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var t = Convert.ToInt64(Math.Round(double.Parse(reader.Value.ToString()) * 1000));
|
if (reader.Value.GetType() == typeof(double))
|
||||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(t);
|
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds((double)reader.Value);
|
||||||
|
|
||||||
|
var t = double.Parse(reader.Value.ToString(), CultureInfo.InvariantCulture);
|
||||||
|
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
32
CryptoExchange.Net/Converters/UTCDateTimeConverter.cs
Normal file
32
CryptoExchange.Net/Converters/UTCDateTimeConverter.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Converters
|
||||||
|
{
|
||||||
|
public class UTCDateTimeConverter: JsonConverter
|
||||||
|
{
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
writer.WriteValue(JsonConvert.SerializeObject(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (reader.Value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
DateTime value;
|
||||||
|
if (reader.Value is string)
|
||||||
|
value = (DateTime)JsonConvert.DeserializeObject((string)reader.Value);
|
||||||
|
else
|
||||||
|
value = (DateTime) reader.Value;
|
||||||
|
|
||||||
|
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<PackageVersion>0.0.18</PackageVersion>
|
<PackageVersion>0.0.36</PackageVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE</PackageLicenseUrl>
|
||||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CryptoExchange.Net.Attributes;
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
@ -16,7 +17,7 @@ using Newtonsoft.Json.Linq;
|
|||||||
|
|
||||||
namespace CryptoExchange.Net
|
namespace CryptoExchange.Net
|
||||||
{
|
{
|
||||||
public abstract class ExchangeClient: IDisposable
|
public abstract class ExchangeClient : IDisposable
|
||||||
{
|
{
|
||||||
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
|
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
|
||||||
|
|
||||||
@ -27,6 +28,11 @@ namespace CryptoExchange.Net
|
|||||||
protected AuthenticationProvider authProvider;
|
protected AuthenticationProvider authProvider;
|
||||||
private List<IRateLimiter> rateLimiters;
|
private List<IRateLimiter> rateLimiters;
|
||||||
|
|
||||||
|
private static JsonSerializer defaultSerializer = JsonSerializer.Create(new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
DateTimeZoneHandling = DateTimeZoneHandling.Utc
|
||||||
|
});
|
||||||
|
|
||||||
protected ExchangeClient(ExchangeOptions exchangeOptions, AuthenticationProvider authenticationProvider)
|
protected ExchangeClient(ExchangeOptions exchangeOptions, AuthenticationProvider authenticationProvider)
|
||||||
{
|
{
|
||||||
log = new Log();
|
log = new Log();
|
||||||
@ -44,7 +50,7 @@ namespace CryptoExchange.Net
|
|||||||
log.Level = exchangeOptions.LogVerbosity;
|
log.Level = exchangeOptions.LogVerbosity;
|
||||||
|
|
||||||
apiProxy = exchangeOptions.Proxy;
|
apiProxy = exchangeOptions.Proxy;
|
||||||
if(apiProxy != null)
|
if (apiProxy != null)
|
||||||
log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}");
|
log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}");
|
||||||
|
|
||||||
rateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
|
rateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
|
||||||
@ -82,6 +88,7 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
protected virtual async Task<CallResult<T>> ExecuteRequest<T>(Uri uri, string method = "GET", Dictionary<string, object> parameters = null, bool signed = false) where T : class
|
protected virtual async Task<CallResult<T>> ExecuteRequest<T>(Uri uri, string method = "GET", Dictionary<string, object> parameters = null, bool signed = false) where T : class
|
||||||
{
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, $"Creating request for " + uri);
|
||||||
if (signed && authProvider == null)
|
if (signed && authProvider == null)
|
||||||
{
|
{
|
||||||
log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
|
log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
|
||||||
@ -91,7 +98,10 @@ namespace CryptoExchange.Net
|
|||||||
var request = ConstructRequest(uri, method, parameters, signed);
|
var request = ConstructRequest(uri, method, parameters, signed);
|
||||||
|
|
||||||
if (apiProxy != null)
|
if (apiProxy != null)
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, "Setting proxy");
|
||||||
request.SetProxy(apiProxy.Host, apiProxy.Port);
|
request.SetProxy(apiProxy.Host, apiProxy.Port);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var limiter in rateLimiters)
|
foreach (var limiter in rateLimiters)
|
||||||
{
|
{
|
||||||
@ -105,7 +115,18 @@ namespace CryptoExchange.Net
|
|||||||
log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}");
|
log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Write(LogVerbosity.Debug, $"Sending {(signed ? "signed": "")} request to {request.Uri}");
|
string paramString = null;
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
paramString = "with parameters";
|
||||||
|
|
||||||
|
foreach (var param in parameters)
|
||||||
|
paramString += $" {param.Key}={(param.Value.GetType().IsArray ? $"[{string.Join(", ", ((object[])param.Value).Select(p => p.ToString()))}]": param.Value )},";
|
||||||
|
|
||||||
|
paramString = paramString.Trim(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Write(LogVerbosity.Debug, $"Sending {(signed ? "signed" : "")} request to {request.Uri} {(paramString ?? "")}");
|
||||||
var result = await ExecuteRequest(request).ConfigureAwait(false);
|
var result = await ExecuteRequest(request).ConfigureAwait(false);
|
||||||
return result.Error != null ? new CallResult<T>(null, result.Error) : Deserialize<T>(result.Data);
|
return result.Error != null ? new CallResult<T>(null, result.Error) : Deserialize<T>(result.Data);
|
||||||
}
|
}
|
||||||
@ -119,7 +140,14 @@ namespace CryptoExchange.Net
|
|||||||
if (!uriString.EndsWith("?"))
|
if (!uriString.EndsWith("?"))
|
||||||
uriString += "?";
|
uriString += "?";
|
||||||
|
|
||||||
uriString += $"{string.Join("&", parameters.Select(s => $"{s.Key}={s.Value}"))}";
|
var arraysParameters = parameters.Where(p => p.Value.GetType().IsArray).ToList();
|
||||||
|
foreach(var arrayEntry in arraysParameters)
|
||||||
|
{
|
||||||
|
uriString += $"{string.Join("&", ((object[])arrayEntry.Value).Select(v => $"{arrayEntry.Key}[]={v}"))}&";
|
||||||
|
}
|
||||||
|
|
||||||
|
uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={s.Value}"))}";
|
||||||
|
uriString = uriString.TrimEnd('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authProvider != null)
|
if (authProvider != null)
|
||||||
@ -185,8 +213,11 @@ namespace CryptoExchange.Net
|
|||||||
return new ServerError(error);
|
return new ServerError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CallResult<T> Deserialize<T>(string data, bool checkObject = true) where T: class
|
protected CallResult<T> Deserialize<T>(string data, bool checkObject = true, JsonSerializer serializer = null) where T : class
|
||||||
{
|
{
|
||||||
|
if (serializer == null)
|
||||||
|
serializer = defaultSerializer;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var obj = JToken.Parse(data);
|
var obj = JToken.Parse(data);
|
||||||
@ -195,10 +226,12 @@ namespace CryptoExchange.Net
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (obj is JObject o)
|
if (obj is JObject o)
|
||||||
|
{
|
||||||
CheckObject(typeof(T), o);
|
CheckObject(typeof(T), o);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var ary = (JArray) obj;
|
var ary = (JArray)obj;
|
||||||
if (ary.HasValues && ary[0] is JObject jObject)
|
if (ary.HasValues && ary[0] is JObject jObject)
|
||||||
CheckObject(typeof(T).GetElementType(), jObject);
|
CheckObject(typeof(T).GetElementType(), jObject);
|
||||||
}
|
}
|
||||||
@ -209,17 +242,23 @@ namespace CryptoExchange.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CallResult<T>(obj.ToObject<T>(), null);
|
return new CallResult<T>(obj.ToObject<T>(serializer), null);
|
||||||
}
|
}
|
||||||
catch (JsonReaderException jre)
|
catch (JsonReaderException jre)
|
||||||
{
|
{
|
||||||
var info = $"{jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {data}";
|
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {data}";
|
||||||
log.Write(LogVerbosity.Error, info);
|
log.Write(LogVerbosity.Error, info);
|
||||||
return new CallResult<T>(null, new DeserializeError(info));
|
return new CallResult<T>(null, new DeserializeError(info));
|
||||||
}
|
}
|
||||||
catch (JsonSerializationException jse)
|
catch (JsonSerializationException jse)
|
||||||
{
|
{
|
||||||
var info = $"{jse.Message}. Received data: {data}";
|
var info = $"Deserialize JsonSerializationException: {jse.Message}. Received data: {data}";
|
||||||
|
log.Write(LogVerbosity.Error, info);
|
||||||
|
return new CallResult<T>(null, new DeserializeError(info));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var info = $"Deserialize Unknown Exception: {ex.Message}. Received data: {data}";
|
||||||
log.Write(LogVerbosity.Error, info);
|
log.Write(LogVerbosity.Error, info);
|
||||||
return new CallResult<T>(null, new DeserializeError(info));
|
return new CallResult<T>(null, new DeserializeError(info));
|
||||||
}
|
}
|
||||||
@ -227,6 +266,19 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
private void CheckObject(Type type, JObject obj)
|
private void CheckObject(Type type, JObject obj)
|
||||||
{
|
{
|
||||||
|
if (type.GetCustomAttribute<JsonConverterAttribute>(true) != null)
|
||||||
|
// If type has a custom JsonConverter we assume this will handle property mapping
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!obj.HasValues && type != typeof(object))
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Warning, $"Expected `{type.Name}`, but received object was empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool isDif = false;
|
bool isDif = false;
|
||||||
var properties = new List<string>();
|
var properties = new List<string>();
|
||||||
var props = type.GetProperties();
|
var props = type.GetProperties();
|
||||||
@ -237,7 +289,7 @@ namespace CryptoExchange.Net
|
|||||||
if (ignore != null)
|
if (ignore != null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
properties.Add(attr == null ? prop.Name : ((JsonPropertyAttribute) attr).PropertyName);
|
properties.Add(attr == null ? prop.Name : ((JsonPropertyAttribute)attr).PropertyName);
|
||||||
}
|
}
|
||||||
foreach (var token in obj)
|
foreach (var token in obj)
|
||||||
{
|
{
|
||||||
@ -247,7 +299,7 @@ namespace CryptoExchange.Net
|
|||||||
d = properties.SingleOrDefault(p => p.ToLower() == token.Key.ToLower());
|
d = properties.SingleOrDefault(p => p.ToLower() == token.Key.ToLower());
|
||||||
if (d == null && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)))
|
if (d == null && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)))
|
||||||
{
|
{
|
||||||
log.Write(LogVerbosity.Warning, $"Didn't find property `{token.Key}` in object of type `{type.Name}`");
|
log.Write(LogVerbosity.Warning, $"Local object doesn't have property `{token.Key}` expected in type `{type.Name}`");
|
||||||
isDif = true;
|
isDif = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -259,20 +311,26 @@ namespace CryptoExchange.Net
|
|||||||
continue;
|
continue;
|
||||||
if (!IsSimple(propType) && propType != typeof(DateTime))
|
if (!IsSimple(propType) && propType != typeof(DateTime))
|
||||||
{
|
{
|
||||||
if(propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject)
|
if (propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject)
|
||||||
CheckObject(propType.GetElementType(), (JObject)token.Value[0]);
|
CheckObject(propType.GetElementType(), (JObject)token.Value[0]);
|
||||||
else if(token.Value is JObject)
|
else if (token.Value is JObject)
|
||||||
CheckObject(propType, (JObject)token.Value);
|
CheckObject(propType, (JObject)token.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
{
|
{
|
||||||
|
var propInfo = props.FirstOrDefault(p => p.Name == prop ||
|
||||||
|
((JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault())?.PropertyName == prop);
|
||||||
|
var optional = propInfo.GetCustomAttributes(typeof(JsonOptionalPropertyAttribute), false).FirstOrDefault();
|
||||||
|
if (optional != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
isDif = true;
|
isDif = true;
|
||||||
log.Write(LogVerbosity.Warning, $"Didn't find key `{prop}` in returned data object of type `{type.Name}`");
|
log.Write(LogVerbosity.Warning, $"Local object has property `{prop}` but was not found in received object of type `{type.Name}`");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isDif)
|
if (isDif)
|
||||||
log.Write(LogVerbosity.Debug, "Returned data: " + obj);
|
log.Write(LogVerbosity.Debug, "Returned data: " + obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +346,7 @@ namespace CryptoExchange.Net
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (((JsonPropertyAttribute) attr).PropertyName == name)
|
if (((JsonPropertyAttribute)attr).PropertyName == name)
|
||||||
return prop;
|
return prop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,6 +368,7 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
public virtual void Dispose()
|
public virtual void Dispose()
|
||||||
{
|
{
|
||||||
|
authProvider?.Credentials?.Dispose();
|
||||||
log.Write(LogVerbosity.Debug, "Disposing exchange client");
|
log.Write(LogVerbosity.Debug, "Disposing exchange client");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace CryptoExchange.Net
|
namespace CryptoExchange.Net
|
||||||
{
|
{
|
||||||
@ -25,5 +28,30 @@ namespace CryptoExchange.Net
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
parameters.Add(key, value);
|
parameters.Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetString(this SecureString source)
|
||||||
|
{
|
||||||
|
string result = null;
|
||||||
|
int length = source.Length;
|
||||||
|
IntPtr pointer = IntPtr.Zero;
|
||||||
|
char[] chars = new char[length];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pointer = Marshal.SecureStringToBSTR(source);
|
||||||
|
Marshal.Copy(pointer, chars, 0, length);
|
||||||
|
|
||||||
|
result = string.Join("", chars);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (pointer != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.ZeroFreeBSTR(pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using CryptoExchange.Net.Logging;
|
||||||
|
using SuperSocket.ClientEngine;
|
||||||
using SuperSocket.ClientEngine.Proxy;
|
using SuperSocket.ClientEngine.Proxy;
|
||||||
using WebSocket4Net;
|
using WebSocket4Net;
|
||||||
|
|
||||||
@ -14,6 +17,8 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
public class BaseSocket: IWebsocket
|
public class BaseSocket: IWebsocket
|
||||||
{
|
{
|
||||||
protected WebSocket socket;
|
protected WebSocket socket;
|
||||||
|
protected Log log;
|
||||||
|
protected object socketLock = new object();
|
||||||
|
|
||||||
protected readonly List<Action<Exception>> errorhandlers = new List<Action<Exception>>();
|
protected readonly List<Action<Exception>> errorhandlers = new List<Action<Exception>>();
|
||||||
protected readonly List<Action> openhandlers = new List<Action>();
|
protected readonly List<Action> openhandlers = new List<Action>();
|
||||||
@ -35,12 +40,13 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
set => socket.AutoSendPingInterval = (int) Math.Round(value.TotalSeconds);
|
set => socket.AutoSendPingInterval = (int) Math.Round(value.TotalSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseSocket(string url):this(url, new Dictionary<string, string>(), new Dictionary<string, string>())
|
public BaseSocket(Log log, string url):this(log, url, new Dictionary<string, string>(), new Dictionary<string, string>())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseSocket(string url, IDictionary<string, string> cookies, IDictionary<string, string> headers)
|
public BaseSocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers)
|
||||||
{
|
{
|
||||||
|
this.log = log;
|
||||||
socket = new WebSocket(url, cookies: cookies.ToList(), customHeaderItems: headers.ToList());
|
socket = new WebSocket(url, cookies: cookies.ToList(), customHeaderItems: headers.ToList());
|
||||||
socket.EnableAutoSendPing = true;
|
socket.EnableAutoSendPing = true;
|
||||||
socket.AutoSendPingInterval = 10;
|
socket.AutoSendPingInterval = 10;
|
||||||
@ -70,7 +76,7 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
public event Action OnOpen
|
public event Action OnOpen
|
||||||
{
|
{
|
||||||
add => openhandlers.Add(value);
|
add => openhandlers.Add(value);
|
||||||
remove => closehandlers.Remove(value);
|
remove => openhandlers.Remove(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void Handle(List<Action> handlers)
|
protected static void Handle(List<Action> handlers)
|
||||||
@ -89,12 +95,23 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
{
|
{
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
lock (socketLock)
|
||||||
|
{
|
||||||
|
if (socket == null || IsClosed)
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, "Socket was already closed/disposed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Write(LogVerbosity.Debug, "Closing websocket");
|
||||||
ManualResetEvent evnt = new ManualResetEvent(false);
|
ManualResetEvent evnt = new ManualResetEvent(false);
|
||||||
var handler = new EventHandler((o, a) => evnt.Set());
|
var handler = new EventHandler((o, a) => evnt.Set());
|
||||||
socket.Closed += handler;
|
socket.Closed += handler;
|
||||||
socket.Close();
|
socket.Close();
|
||||||
evnt.WaitOne();
|
bool triggered = evnt.WaitOne(3000);
|
||||||
socket.Closed -= handler;
|
socket.Closed -= handler;
|
||||||
|
log.Write(LogVerbosity.Debug, "Websocket closed");
|
||||||
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,15 +124,34 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
{
|
{
|
||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
bool connected;
|
||||||
|
lock (socketLock)
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, "Connecting websocket");
|
||||||
ManualResetEvent evnt = new ManualResetEvent(false);
|
ManualResetEvent evnt = new ManualResetEvent(false);
|
||||||
var handler = new EventHandler((o, a) => evnt.Set());
|
var handler = new EventHandler((o, a) => evnt?.Set());
|
||||||
|
var errorHandler = new EventHandler<ErrorEventArgs>((o, a) => evnt?.Set());
|
||||||
socket.Opened += handler;
|
socket.Opened += handler;
|
||||||
socket.Closed += handler;
|
socket.Closed += handler;
|
||||||
|
socket.Error += errorHandler;
|
||||||
socket.Open();
|
socket.Open();
|
||||||
evnt.WaitOne();
|
evnt.WaitOne(TimeSpan.FromSeconds(15));
|
||||||
socket.Opened -= handler;
|
socket.Opened -= handler;
|
||||||
socket.Closed -= handler;
|
socket.Closed -= handler;
|
||||||
return socket.State == WebSocketState.Open;
|
socket.Error -= errorHandler;
|
||||||
|
connected = socket.State == WebSocketState.Open;
|
||||||
|
if (connected)
|
||||||
|
log.Write(LogVerbosity.Debug, "Websocket connected");
|
||||||
|
else
|
||||||
|
log.Write(LogVerbosity.Debug, "Websocket connection failed, state: " + socket.State);
|
||||||
|
evnt.Dispose();
|
||||||
|
evnt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket.State == WebSocketState.Connecting)
|
||||||
|
Close().Wait();
|
||||||
|
|
||||||
|
return connected;
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +170,13 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
lock (socketLock)
|
||||||
|
{
|
||||||
|
if (socket != null)
|
||||||
|
log.Write(LogVerbosity.Debug, "Disposing websocket");
|
||||||
|
|
||||||
socket?.Dispose();
|
socket?.Dispose();
|
||||||
|
socket = null;
|
||||||
|
|
||||||
errorhandlers.Clear();
|
errorhandlers.Clear();
|
||||||
openhandlers.Clear();
|
openhandlers.Clear();
|
||||||
@ -142,4 +184,5 @@ namespace CryptoExchange.Net.Implementation
|
|||||||
messagehandlers.Clear();
|
messagehandlers.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
87
CryptoExchange.Net/Implementation/TestWebsocket.cs
Normal file
87
CryptoExchange.Net/Implementation/TestWebsocket.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Implementation
|
||||||
|
{
|
||||||
|
public class TestWebsocket: IWebsocket
|
||||||
|
{
|
||||||
|
public List<string> MessagesSend = new List<string>();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEnabledSslProtocols(SslProtocols protocols)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProxy(string host, int port)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action OnClose;
|
||||||
|
public event Action<string> OnMessage;
|
||||||
|
public event Action<Exception> OnError;
|
||||||
|
public event Action OnOpen;
|
||||||
|
|
||||||
|
public bool IsClosed { get; private set; } = true;
|
||||||
|
public bool IsOpen { get; private set; }
|
||||||
|
public bool PingConnection { get; set; }
|
||||||
|
public TimeSpan PingInterval { get; set; }
|
||||||
|
|
||||||
|
public bool HasConnection = true;
|
||||||
|
|
||||||
|
public Task<bool> Connect()
|
||||||
|
{
|
||||||
|
if (!HasConnection)
|
||||||
|
{
|
||||||
|
OnError(new Exception("No connection"));
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsClosed = false;
|
||||||
|
IsOpen = true;
|
||||||
|
OnOpen?.Invoke();
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(string data)
|
||||||
|
{
|
||||||
|
if (!HasConnection)
|
||||||
|
{
|
||||||
|
OnError(new Exception("No connection"));
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessagesSend.Add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EnqueueMessage(string data, int wait)
|
||||||
|
{
|
||||||
|
await Task.Delay(wait);
|
||||||
|
OnMessage?.Invoke(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeError(Exception ex, bool closeConnection)
|
||||||
|
{
|
||||||
|
await Task.Delay(10);
|
||||||
|
OnError?.Invoke(ex);
|
||||||
|
if (closeConnection)
|
||||||
|
await Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Close()
|
||||||
|
{
|
||||||
|
IsClosed = true;
|
||||||
|
IsOpen = false;
|
||||||
|
OnClose?.Invoke();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
CryptoExchange.Net/Implementation/TestWebsocketFactory.cs
Normal file
13
CryptoExchange.Net/Implementation/TestWebsocketFactory.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using CryptoExchange.Net.Logging;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Implementation
|
||||||
|
{
|
||||||
|
public class TestWebsocketFactory : IWebsocketFactory
|
||||||
|
{
|
||||||
|
public IWebsocket CreateWebsocket(Log log, string url)
|
||||||
|
{
|
||||||
|
return new TestWebsocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using CryptoExchange.Net.Logging;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Implementation
|
namespace CryptoExchange.Net.Implementation
|
||||||
{
|
{
|
||||||
public class WebsocketFactory : IWebsocketFactory
|
public class WebsocketFactory : IWebsocketFactory
|
||||||
{
|
{
|
||||||
public IWebsocket CreateWebsocket(string url)
|
public IWebsocket CreateWebsocket(Log log, string url)
|
||||||
{
|
{
|
||||||
return new BaseSocket(url);
|
return new BaseSocket(log, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
namespace CryptoExchange.Net.Interfaces
|
using CryptoExchange.Net.Logging;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Interfaces
|
||||||
{
|
{
|
||||||
public interface IWebsocketFactory
|
public interface IWebsocketFactory
|
||||||
{
|
{
|
||||||
IWebsocket CreateWebsocket(string url);
|
IWebsocket CreateWebsocket(Log log, string url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ namespace CryptoExchange.Net.Logging
|
|||||||
private List<TextWriter> writers;
|
private List<TextWriter> writers;
|
||||||
private LogVerbosity level = LogVerbosity.Info;
|
private LogVerbosity level = LogVerbosity.Info;
|
||||||
|
|
||||||
|
|
||||||
public LogVerbosity Level
|
public LogVerbosity Level
|
||||||
{
|
{
|
||||||
get => level;
|
get => level;
|
||||||
@ -36,16 +37,19 @@ namespace CryptoExchange.Net.Logging
|
|||||||
|
|
||||||
public void Write(LogVerbosity logType, string message)
|
public void Write(LogVerbosity logType, string message)
|
||||||
{
|
{
|
||||||
foreach (var writer in writers)
|
if ((int)logType < (int)Level)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string logMessage = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | {logType} | {message}";
|
||||||
|
foreach (var writer in writers.ToList())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if ((int) logType >= (int) Level)
|
writer.WriteLine(logMessage);
|
||||||
writer.WriteLine($"{DateTime.Now:yyyy/MM/dd hh:mm:ss:fff} | {logType} | {message}");
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.WriteLine("Failed to write log: " + e.Message);
|
Debug.WriteLine($"Failed to write log to writer {writer.GetType()}: " + e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
CryptoExchange.Net/Logging/ThreadSafeFileWriter.cs
Normal file
44
CryptoExchange.Net/Logging/ThreadSafeFileWriter.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Logging
|
||||||
|
{
|
||||||
|
public class ThreadSafeFileWriter: TextWriter
|
||||||
|
{
|
||||||
|
private static object openedFilesLock = new object();
|
||||||
|
private static List<string> openedFiles = new List<string>();
|
||||||
|
|
||||||
|
private StreamWriter logWriter;
|
||||||
|
private object writeLock;
|
||||||
|
|
||||||
|
public override Encoding Encoding => Encoding.ASCII;
|
||||||
|
|
||||||
|
public ThreadSafeFileWriter(string path)
|
||||||
|
{
|
||||||
|
logWriter = new StreamWriter(File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite));
|
||||||
|
logWriter.AutoFlush = true;
|
||||||
|
writeLock = new object();
|
||||||
|
|
||||||
|
lock(openedFilesLock)
|
||||||
|
{
|
||||||
|
if (openedFiles.Contains(path))
|
||||||
|
throw new System.Exception("Can't have multiple ThreadSafeFileWriters for the same file, reuse a single instance");
|
||||||
|
|
||||||
|
openedFiles.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteLine(string logMessage)
|
||||||
|
{
|
||||||
|
lock(writeLock)
|
||||||
|
logWriter.WriteLine(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
logWriter.Close();
|
||||||
|
logWriter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ namespace CryptoExchange.Net.Requests
|
|||||||
|
|
||||||
public async Task<Stream> GetRequestStream()
|
public async Task<Stream> GetRequestStream()
|
||||||
{
|
{
|
||||||
return await request.GetRequestStreamAsync();
|
return await request.GetRequestStreamAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IResponse> GetResponse()
|
public async Task<IResponse> GetResponse()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user