1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-12 02:16:23 +00:00

Added code docs, added ContinueOnQueryResponse

This commit is contained in:
Jan Korf 2019-08-06 13:19:34 +02:00
parent 11016bc213
commit 780da53475
50 changed files with 2545 additions and 30 deletions

View File

@ -2,6 +2,9 @@
namespace CryptoExchange.Net.Attributes namespace CryptoExchange.Net.Attributes
{ {
/// <summary>
/// Marks property as optional
/// </summary>
public class JsonOptionalPropertyAttribute : Attribute public class JsonOptionalPropertyAttribute : Attribute
{ {
} }

View File

@ -6,6 +6,9 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net.Authentication namespace CryptoExchange.Net.Authentication
{ {
/// <summary>
/// Api credentials info
/// </summary>
public class ApiCredentials: IDisposable public class ApiCredentials: IDisposable
{ {
/// <summary> /// <summary>
@ -85,14 +88,14 @@ namespace CryptoExchange.Net.Authentication
inputStream.Seek(0, SeekOrigin.Begin); inputStream.Seek(0, SeekOrigin.Begin);
} }
protected string TryGetValue(JToken data, string key) private string TryGetValue(JToken data, string key)
{ {
if (data[key] == null) if (data[key] == null)
return null; return null;
return (string) data[key]; return (string) data[key];
} }
protected SecureString CreateSecureString(string source) private SecureString CreateSecureString(string source)
{ {
var secureString = new SecureString(); var secureString = new SecureString();
foreach (var c in source) foreach (var c in source)
@ -101,6 +104,9 @@ namespace CryptoExchange.Net.Authentication
return secureString; return secureString;
} }
/// <summary>
/// Dispose
/// </summary>
public void Dispose() public void Dispose()
{ {
Key?.Dispose(); Key?.Dispose();

View File

@ -2,35 +2,76 @@
namespace CryptoExchange.Net.Authentication namespace CryptoExchange.Net.Authentication
{ {
/// <summary>
/// Base class for authentication providers
/// </summary>
public abstract class AuthenticationProvider public abstract class AuthenticationProvider
{ {
/// <summary>
/// The provided credentials
/// </summary>
public ApiCredentials Credentials { get; } public ApiCredentials Credentials { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="credentials"></param>
protected AuthenticationProvider(ApiCredentials credentials) protected AuthenticationProvider(ApiCredentials credentials)
{ {
Credentials = credentials; Credentials = credentials;
} }
/// <summary>
/// Add authentication to the parameter list
/// </summary>
/// <param name="uri"></param>
/// <param name="method"></param>
/// <param name="parameters"></param>
/// <param name="signed"></param>
/// <returns></returns>
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, string method, Dictionary<string, object> parameters, bool signed) public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, string method, Dictionary<string, object> parameters, bool signed)
{ {
return parameters; return parameters;
} }
/// <summary>
/// Add authentication to the header dictionary
/// </summary>
/// <param name="uri"></param>
/// <param name="method"></param>
/// <param name="parameters"></param>
/// <param name="signed"></param>
/// <returns></returns>
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, string method, Dictionary<string, object> parameters, bool signed) public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, string method, Dictionary<string, object> parameters, bool signed)
{ {
return new Dictionary<string, string>(); return new Dictionary<string, string>();
} }
/// <summary>
/// Sign a string
/// </summary>
/// <param name="toSign"></param>
/// <returns></returns>
public virtual string Sign(string toSign) public virtual string Sign(string toSign)
{ {
return toSign; return toSign;
} }
/// <summary>
/// Sign a byte array
/// </summary>
/// <param name="toSign"></param>
/// <returns></returns>
public virtual byte[] Sign(byte[] toSign) public virtual byte[] Sign(byte[] toSign)
{ {
return toSign; return toSign;
} }
/// <summary>
/// Convert byte array to hex
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected string ByteToString(byte[] buff) protected string ByteToString(byte[] buff)
{ {
var result = ""; var result = "";

View File

@ -3,6 +3,9 @@ using System.Security;
namespace CryptoExchange.Net.Authentication namespace CryptoExchange.Net.Authentication
{ {
/// <summary>
/// Private key info
/// </summary>
public class PrivateKey : IDisposable public class PrivateKey : IDisposable
{ {
/// <summary> /// <summary>
@ -87,6 +90,9 @@ namespace CryptoExchange.Net.Authentication
IsEncrypted = false; IsEncrypted = false;
} }
/// <summary>
/// Dispose
/// </summary>
public void Dispose() public void Dispose()
{ {
Key?.Dispose(); Key?.Dispose();

View File

@ -12,14 +12,35 @@ using System.Reflection;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
/// <summary>
/// The base for all clients
/// </summary>
public abstract class BaseClient: IDisposable public abstract class BaseClient: IDisposable
{ {
/// <summary>
/// The address of the client
/// </summary>
public string BaseAddress { get; private set; } public string BaseAddress { get; private set; }
/// <summary>
/// The log object
/// </summary>
protected internal Log log; protected internal Log log;
/// <summary>
/// The api proxy
/// </summary>
protected ApiProxy apiProxy; protected ApiProxy apiProxy;
/// <summary>
/// The auth provider
/// </summary>
protected internal AuthenticationProvider authProvider; protected internal AuthenticationProvider authProvider;
/// <summary>
/// The last used id
/// </summary>
protected static int lastId; protected static int lastId;
/// <summary>
/// Lock for id generating
/// </summary>
protected static object idLock = new object(); protected static object idLock = new object();
private static readonly JsonSerializer defaultSerializer = JsonSerializer.Create(new JsonSerializerSettings private static readonly JsonSerializer defaultSerializer = JsonSerializer.Create(new JsonSerializerSettings
@ -28,8 +49,16 @@ namespace CryptoExchange.Net
Culture = CultureInfo.InvariantCulture Culture = CultureInfo.InvariantCulture
}); });
/// <summary>
/// Last is used
/// </summary>
public static int LastId => lastId; public static int LastId => lastId;
/// <summary>
/// ctor
/// </summary>
/// <param name="options"></param>
/// <param name="authenticationProvider"></param>
protected BaseClient(ClientOptions options, AuthenticationProvider authenticationProvider) protected BaseClient(ClientOptions options, AuthenticationProvider authenticationProvider)
{ {
log = new Log(); log = new Log();
@ -40,7 +69,7 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// Configure the client using the provided options /// Configure the client using the provided options
/// </summary> /// </summary>
/// <param name="clientOptionsns">Options</param> /// <param name="clientOptions">Options</param>
protected void Configure(ClientOptions clientOptions) protected void Configure(ClientOptions clientOptions)
{ {
log.UpdateWriters(clientOptions.LogWriters); log.UpdateWriters(clientOptions.LogWriters);
@ -306,6 +335,9 @@ namespace CryptoExchange.Net
return path; return path;
} }
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose() public virtual void Dispose()
{ {
authProvider?.Credentials?.Dispose(); authProvider?.Credentials?.Dispose();

View File

@ -8,13 +8,18 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// Converter for arrays to properties
/// </summary>
public class ArrayConverter : JsonConverter public class ArrayConverter : JsonConverter
{ {
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return true; return true;
} }
/// <inheritdoc />
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 (objectType == typeof(JToken)) if (objectType == typeof(JToken))
@ -95,6 +100,7 @@ namespace CryptoExchange.Net.Converters
return result; return result;
} }
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
writer.WriteStartArray(); writer.WriteStartArray();
@ -143,10 +149,20 @@ namespace CryptoExchange.Net.Converters
} }
} }
/// <summary>
/// Mark property as an index in the array
/// </summary>
public class ArrayPropertyAttribute: Attribute public class ArrayPropertyAttribute: Attribute
{ {
/// <summary>
/// The index in the array
/// </summary>
public int Index { get; } public int Index { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="index"></param>
public ArrayPropertyAttribute(int index) public ArrayPropertyAttribute(int index)
{ {
Index = index; Index = index;

View File

@ -6,16 +6,28 @@ using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// Base class for enum converters
/// </summary>
/// <typeparam name="T">Type of enum to convert</typeparam>
public abstract class BaseConverter<T>: JsonConverter public abstract class BaseConverter<T>: JsonConverter
{ {
/// <summary>
/// The enum->string mapping
/// </summary>
protected abstract List<KeyValuePair<T, string>> Mapping { get; } protected abstract List<KeyValuePair<T, string>> Mapping { get; }
private readonly bool quotes; private readonly bool quotes;
/// <summary>
/// ctor
/// </summary>
/// <param name="useQuotes"></param>
protected BaseConverter(bool useQuotes) protected BaseConverter(bool useQuotes)
{ {
quotes = useQuotes; quotes = useQuotes;
} }
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
var stringValue = GetValue((T) value); var stringValue = GetValue((T) value);
@ -25,6 +37,7 @@ namespace CryptoExchange.Net.Converters
writer.WriteRawValue(stringValue); writer.WriteRawValue(stringValue);
} }
/// <inheritdoc />
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) if (reader.Value == null)
@ -39,11 +52,17 @@ namespace CryptoExchange.Net.Converters
return result; return result;
} }
/// <summary>
/// Convert a string value
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public T ReadString(string data) public T ReadString(string data)
{ {
return Mapping.FirstOrDefault(v => v.Value == data).Key; return Mapping.FirstOrDefault(v => v.Value == data).Key;
} }
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
// Check if it is type, or nullable of type // Check if it is type, or nullable of type

View File

@ -3,13 +3,18 @@ using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// converter for milliseconds to datetime
/// </summary>
public class TimestampConverter : JsonConverter public class TimestampConverter : JsonConverter
{ {
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(DateTime); return objectType == typeof(DateTime);
} }
/// <inheritdoc />
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) if (reader.Value == null)
@ -19,6 +24,7 @@ namespace CryptoExchange.Net.Converters
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);
} }
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalMilliseconds)); writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalMilliseconds));

View File

@ -3,13 +3,18 @@ using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// Converter for nanoseconds to datetime
/// </summary>
public class TimestampNanoSecondsConverter : JsonConverter public class TimestampNanoSecondsConverter : JsonConverter
{ {
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(DateTime); return objectType == typeof(DateTime);
} }
/// <inheritdoc />
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) if (reader.Value == null)
@ -20,6 +25,7 @@ namespace CryptoExchange.Net.Converters
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddTicks((long)Math.Round(nanoSeconds * ticksPerNanosecond)); return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddTicks((long)Math.Round(nanoSeconds * ticksPerNanosecond));
} }
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
var ticksPerNanosecond = (TimeSpan.TicksPerMillisecond / 1000m / 1000); var ticksPerNanosecond = (TimeSpan.TicksPerMillisecond / 1000m / 1000);

View File

@ -4,13 +4,18 @@ using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// Converter for seconds to datetime
/// </summary>
public class TimestampSecondsConverter : JsonConverter public class TimestampSecondsConverter : JsonConverter
{ {
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(DateTime); return objectType == typeof(DateTime);
} }
/// <inheritdoc />
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 is double d) if (reader.Value is double d)
@ -20,6 +25,7 @@ namespace CryptoExchange.Net.Converters
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(t); return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(t);
} }
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalSeconds)); writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalSeconds));

View File

@ -3,13 +3,18 @@ using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// Converter for utc datetime
/// </summary>
public class UTCDateTimeConverter: JsonConverter public class UTCDateTimeConverter: JsonConverter
{ {
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ {
writer.WriteValue(JsonConvert.SerializeObject(value)); writer.WriteValue(JsonConvert.SerializeObject(value));
} }
/// <inheritdoc />
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) if (reader.Value == null)
@ -24,6 +29,7 @@ namespace CryptoExchange.Net.Converters
return DateTime.SpecifyKind(value, DateTimeKind.Utc); return DateTime.SpecifyKind(value, DateTimeKind.Utc);
} }
/// <inheritdoc />
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(DateTime) || objectType == typeof(DateTime?); return objectType == typeof(DateTime) || objectType == typeof(DateTime?);

View File

@ -1,27 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<PackageVersion>2.1.5</PackageVersion> <PackageVersion>2.1.6</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>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>2.1.6 - Fix for missing subscription events if they are also a request response, added code docs</PackageReleaseNotes>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile> <DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="WebSocket4Net" Version="0.15.2" /> <PackageReference Include="WebSocket4Net" Version="0.15.2" />
</ItemGroup> </ItemGroup>
</Project>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -13,24 +13,51 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
/// <summary>
/// Helper methods
/// </summary>
public static class ExtensionMethods public static class ExtensionMethods
{ {
/// <summary>
/// Add a parameter
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, string value) public static void AddParameter(this Dictionary<string, object> parameters, string key, string value)
{ {
parameters.Add(key, value); parameters.Add(key, value);
} }
/// <summary>
/// Add a parameter
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddParameter(this Dictionary<string, object> parameters, string key, object value) public static void AddParameter(this Dictionary<string, object> parameters, string key, object value)
{ {
parameters.Add(key, value); parameters.Add(key, value);
} }
/// <summary>
/// Add an optional parameter. Not added if value is null
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddOptionalParameter(this Dictionary<string, object> parameters, string key, object value) public static void AddOptionalParameter(this Dictionary<string, object> parameters, string key, object value)
{ {
if(value != null) if(value != null)
parameters.Add(key, value); parameters.Add(key, value);
} }
/// <summary>
/// Add an optional parameter. Not added if value is null
/// </summary>
/// <param name="parameters"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void AddOptionalParameter(this Dictionary<string, string> parameters, string key, string value) public static void AddOptionalParameter(this Dictionary<string, string> parameters, string key, string value)
{ {
if (value != null) if (value != null)
@ -90,6 +117,11 @@ namespace CryptoExchange.Net
} }
} }
/// <summary>
/// Header collection to inenumerable
/// </summary>
/// <param name="headers"></param>
/// <returns></returns>
public static IEnumerable<Tuple<string, string>> ToIEnumerable(this WebHeaderCollection headers) public static IEnumerable<Tuple<string, string>> ToIEnumerable(this WebHeaderCollection headers)
{ {
if (headers == null) if (headers == null)
@ -102,6 +134,13 @@ namespace CryptoExchange.Net
); );
} }
/// <summary>
/// Wait one async
/// </summary>
/// <param name="handle"></param>
/// <param name="millisecondsTimeout"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<bool> WaitOneAsync(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken) public static async Task<bool> WaitOneAsync(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken)
{ {
RegisteredWaitHandle registeredHandle = null; RegisteredWaitHandle registeredHandle = null;
@ -126,12 +165,24 @@ namespace CryptoExchange.Net
tokenRegistration.Dispose(); tokenRegistration.Dispose();
} }
} }
/// <summary>
/// Wait one async
/// </summary>
/// <param name="handle"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public static Task<bool> WaitOneAsync(this WaitHandle handle, TimeSpan timeout) public static Task<bool> WaitOneAsync(this WaitHandle handle, TimeSpan timeout)
{ {
return handle.WaitOneAsync((int)timeout.TotalMilliseconds, CancellationToken.None); return handle.WaitOneAsync((int)timeout.TotalMilliseconds, CancellationToken.None);
} }
/// <summary>
/// String to JToken
/// </summary>
/// <param name="stringData"></param>
/// <param name="log"></param>
/// <returns></returns>
public static JToken ToJToken(this string stringData, Log log = null) public static JToken ToJToken(this string stringData, Log log = null)
{ {
if (string.IsNullOrEmpty(stringData)) if (string.IsNullOrEmpty(stringData))

View File

@ -2,8 +2,18 @@
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
/// <summary>
/// Rate limiter interface
/// </summary>
public interface IRateLimiter public interface IRateLimiter
{ {
/// <summary>
/// Limit the request if needed
/// </summary>
/// <param name="client"></param>
/// <param name="url"></param>
/// <param name="limitBehaviour"></param>
/// <returns></returns>
CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour); CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour);
} }
} }

View File

@ -5,20 +5,62 @@ using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
/// <summary>
/// Request interface
/// </summary>
public interface IRequest public interface IRequest
{ {
/// <summary>
/// The uri of the request
/// </summary>
Uri Uri { get; } Uri Uri { get; }
/// <summary>
/// The headers of the request
/// </summary>
WebHeaderCollection Headers { get; set; } WebHeaderCollection Headers { get; set; }
/// <summary>
/// The method of the request
/// </summary>
string Method { get; set; } string Method { get; set; }
/// <summary>
/// The timeout of the request
/// </summary>
TimeSpan Timeout { get; set; } TimeSpan Timeout { get; set; }
/// <summary>
/// Set a proxy
/// </summary>
/// <param name="host"></param>
/// <param name="port"></param>
/// <param name="login"></param>
/// <param name="password"></param>
void SetProxy(string host, int port, string login, string password); void SetProxy(string host, int port, string login, string password);
/// <summary>
/// Content type
/// </summary>
string ContentType { get; set; } string ContentType { get; set; }
/// <summary>
/// String content
/// </summary>
string Content { get; set; } string Content { get; set; }
/// <summary>
/// Accept
/// </summary>
string Accept { get; set; } string Accept { get; set; }
/// <summary>
/// Content length
/// </summary>
long ContentLength { get; set; } long ContentLength { get; set; }
/// <summary>
/// Get the request stream
/// </summary>
/// <returns></returns>
Task<Stream> GetRequestStream(); Task<Stream> GetRequestStream();
/// <summary>
/// Get the response object
/// </summary>
/// <returns></returns>
Task<IResponse> GetResponse(); Task<IResponse> GetResponse();
} }
} }

View File

@ -1,7 +1,15 @@
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
/// <summary>
/// Request factory interface
/// </summary>
public interface IRequestFactory public interface IRequestFactory
{ {
/// <summary>
/// Create a request for an uri
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
IRequest Create(string uri); IRequest Create(string uri);
} }
} }

View File

@ -5,11 +5,28 @@ using System.Net;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
/// <summary>
/// Response object interface
/// </summary>
public interface IResponse public interface IResponse
{ {
/// <summary>
/// The response status code
/// </summary>
HttpStatusCode StatusCode { get; } HttpStatusCode StatusCode { get; }
/// <summary>
/// Get the response stream
/// </summary>
/// <returns></returns>
Stream GetResponseStream(); Stream GetResponseStream();
/// <summary>
/// Get the response headers
/// </summary>
/// <returns></returns>
IEnumerable<Tuple<string, string>> GetResponseHeaders(); IEnumerable<Tuple<string, string>> GetResponseHeaders();
/// <summary>
/// Close the response
/// </summary>
void Close(); void Close();
} }
} }

View File

@ -5,30 +5,104 @@ using WebSocket4Net;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
/// <summary>
/// Interface for websocket interaction
/// </summary>
public interface IWebsocket: IDisposable public interface IWebsocket: IDisposable
{ {
/// <summary>
/// Websocket closed
/// </summary>
event Action OnClose; event Action OnClose;
/// <summary>
/// Websocket message received
/// </summary>
event Action<string> OnMessage; event Action<string> OnMessage;
/// <summary>
/// Websocket error
/// </summary>
event Action<Exception> OnError; event Action<Exception> OnError;
/// <summary>
/// Websocket opened
/// </summary>
event Action OnOpen; event Action OnOpen;
/// <summary>
/// Id
/// </summary>
int Id { get; } int Id { get; }
/// <summary>
/// Origin
/// </summary>
string Origin { get; set; } string Origin { get; set; }
/// <summary>
/// Reconnecting
/// </summary>
bool Reconnecting { get; set; } bool Reconnecting { get; set; }
/// <summary>
/// Handler for byte data
/// </summary>
Func<byte[], string> DataInterpreterBytes { get; set; } Func<byte[], string> DataInterpreterBytes { get; set; }
/// <summary>
/// Handler for string data
/// </summary>
Func<string, string> DataInterpreterString { get; set; } Func<string, string> DataInterpreterString { get; set; }
/// <summary>
/// Socket url
/// </summary>
string Url { get; } string Url { get; }
/// <summary>
/// State
/// </summary>
WebSocketState SocketState { get; } WebSocketState SocketState { get; }
/// <summary>
/// Is closed
/// </summary>
bool IsClosed { get; } bool IsClosed { get; }
/// <summary>
/// Is open
/// </summary>
bool IsOpen { get; } bool IsOpen { get; }
/// <summary>
/// Should ping connecting
/// </summary>
bool PingConnection { get; set; } bool PingConnection { get; set; }
/// <summary>
/// Interval of pinging
/// </summary>
TimeSpan PingInterval { get; set; } TimeSpan PingInterval { get; set; }
/// <summary>
/// Supported ssl protocols
/// </summary>
SslProtocols SSLProtocols { get; set; } SslProtocols SSLProtocols { get; set; }
/// <summary>
/// Timeout
/// </summary>
TimeSpan Timeout { get; set; } TimeSpan Timeout { get; set; }
/// <summary>
/// Connect the socket
/// </summary>
/// <returns></returns>
Task<bool> Connect(); Task<bool> Connect();
/// <summary>
/// Send data
/// </summary>
/// <param name="data"></param>
void Send(string data); void Send(string data);
/// <summary>
/// Reset socket
/// </summary>
void Reset(); void Reset();
/// <summary>
/// Close the connecting
/// </summary>
/// <returns></returns>
Task Close(); Task Close();
/// <summary>
/// Set proxy
/// </summary>
/// <param name="host"></param>
/// <param name="port"></param>
void SetProxy(string host, int port); void SetProxy(string host, int port);
} }
} }

View File

@ -3,9 +3,26 @@ using CryptoExchange.Net.Logging;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
/// <summary>
/// Websocket factory interface
/// </summary>
public interface IWebsocketFactory public interface IWebsocketFactory
{ {
/// <summary>
/// Create a websocket for an url
/// </summary>
/// <param name="log"></param>
/// <param name="url"></param>
/// <returns></returns>
IWebsocket CreateWebsocket(Log log, string url); IWebsocket CreateWebsocket(Log log, string url);
/// <summary>
/// Create a websocket for an url
/// </summary>
/// <param name="log"></param>
/// <param name="url"></param>
/// <param name="cookies"></param>
/// <param name="headers"></param>
/// <returns></returns>
IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers); IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers);
} }
} }

View File

@ -4,10 +4,15 @@ using System.Text;
namespace CryptoExchange.Net.Logging namespace CryptoExchange.Net.Logging
{ {
/// <summary>
/// Default log writer, writes to debug
/// </summary>
public class DebugTextWriter: TextWriter public class DebugTextWriter: TextWriter
{ {
/// <inheritdoc />
public override Encoding Encoding => Encoding.ASCII; public override Encoding Encoding => Encoding.ASCII;
/// <inheritdoc />
public override void WriteLine(string value) public override void WriteLine(string value)
{ {
Debug.WriteLine(value); Debug.WriteLine(value);

View File

@ -6,22 +6,39 @@ using System.Linq;
namespace CryptoExchange.Net.Logging namespace CryptoExchange.Net.Logging
{ {
/// <summary>
/// Log implementation
/// </summary>
public class Log public class Log
{ {
private List<TextWriter> writers; private List<TextWriter> writers;
/// <summary>
/// The verbosity of the logging
/// </summary>
public LogVerbosity Level { get; set; } = LogVerbosity.Info; public LogVerbosity Level { get; set; } = LogVerbosity.Info;
/// <summary>
/// ctor
/// </summary>
public Log() public Log()
{ {
writers = new List<TextWriter>(); writers = new List<TextWriter>();
} }
/// <summary>
/// Set the writers
/// </summary>
/// <param name="textWriters"></param>
public void UpdateWriters(List<TextWriter> textWriters) public void UpdateWriters(List<TextWriter> textWriters)
{ {
writers = textWriters; writers = textWriters;
} }
/// <summary>
/// Write a log entry
/// </summary>
/// <param name="logType"></param>
/// <param name="message"></param>
public void Write(LogVerbosity logType, string message) public void Write(LogVerbosity logType, string message)
{ {
if ((int)logType < (int)Level) if ((int)logType < (int)Level)
@ -42,12 +59,30 @@ namespace CryptoExchange.Net.Logging
} }
} }
/// <summary>
/// The log verbosity
/// </summary>
public enum LogVerbosity public enum LogVerbosity
{ {
/// <summary>
/// Debug logging
/// </summary>
Debug, Debug,
/// <summary>
/// Info logging
/// </summary>
Info, Info,
/// <summary>
/// Warning logging
/// </summary>
Warning, Warning,
/// <summary>
/// Error logging
/// </summary>
Error, Error,
/// <summary>
/// None, used for disabling logging
/// </summary>
None None
} }
} }

View File

@ -4,6 +4,9 @@ using System.Text;
namespace CryptoExchange.Net.Logging namespace CryptoExchange.Net.Logging
{ {
/// <summary>
/// File writer
/// </summary>
public class ThreadSafeFileWriter: TextWriter public class ThreadSafeFileWriter: TextWriter
{ {
private static readonly object openedFilesLock = new object(); private static readonly object openedFilesLock = new object();
@ -12,8 +15,13 @@ namespace CryptoExchange.Net.Logging
private StreamWriter logWriter; private StreamWriter logWriter;
private readonly object writeLock; private readonly object writeLock;
/// <inheritdoc />
public override Encoding Encoding => Encoding.ASCII; public override Encoding Encoding => Encoding.ASCII;
/// <summary>
/// ctor
/// </summary>
/// <param name="path"></param>
public ThreadSafeFileWriter(string path) public ThreadSafeFileWriter(string path)
{ {
logWriter = new StreamWriter(File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)) {AutoFlush = true}; logWriter = new StreamWriter(File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)) {AutoFlush = true};
@ -28,12 +36,17 @@ namespace CryptoExchange.Net.Logging
} }
} }
/// <inheritdoc />
public override void WriteLine(string logMessage) public override void WriteLine(string logMessage)
{ {
lock(writeLock) lock(writeLock)
logWriter.WriteLine(logMessage); logWriter.WriteLine(logMessage);
} }
/// <summary>
/// Dispose
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
lock (writeLock) lock (writeLock)

View File

@ -2,6 +2,9 @@
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
/// <summary>
/// Proxy info
/// </summary>
public class ApiProxy public class ApiProxy
{ {
/// <summary> /// <summary>

View File

@ -3,8 +3,17 @@ using System.Collections.Generic;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
/// <summary>
/// Comparer for byte order
/// </summary>
public class ByteOrderComparer : IComparer<byte[]> public class ByteOrderComparer : IComparer<byte[]>
{ {
/// <summary>
/// Compare function
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(byte[] x, byte[] y) public int Compare(byte[] x, byte[] y)
{ {
// Shortcuts: If both are null, they are the same. // Shortcuts: If both are null, they are the same.

View File

@ -4,6 +4,10 @@ using System.Net;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
/// <summary>
/// The result of an operation
/// </summary>
/// <typeparam name="T"></typeparam>
public class CallResult<T> public class CallResult<T>
{ {
/// <summary> /// <summary>
@ -19,6 +23,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public bool Success => Error == null; public bool Success => Error == null;
/// <summary>
/// ctor
/// </summary>
/// <param name="data"></param>
/// <param name="error"></param>
public CallResult(T data, Error error) public CallResult(T data, Error error)
{ {
Data = data; Data = data;
@ -26,6 +35,10 @@ namespace CryptoExchange.Net.Objects
} }
} }
/// <summary>
/// The result of a request
/// </summary>
/// <typeparam name="T"></typeparam>
public class WebCallResult<T>: CallResult<T> public class WebCallResult<T>: CallResult<T>
{ {
/// <summary> /// <summary>
@ -33,18 +46,41 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public HttpStatusCode? ResponseStatusCode { get; set; } public HttpStatusCode? ResponseStatusCode { get; set; }
/// <summary>
/// The response headers
/// </summary>
public IEnumerable<Tuple<string, string>> ResponseHeaders { get; set; } public IEnumerable<Tuple<string, string>> ResponseHeaders { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="data"></param>
/// <param name="error"></param>
public WebCallResult(HttpStatusCode? code, IEnumerable<Tuple<string, string>> responseHeaders, T data, Error error): base(data, error) public WebCallResult(HttpStatusCode? code, IEnumerable<Tuple<string, string>> responseHeaders, T data, Error error): base(data, error)
{ {
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseStatusCode = code; ResponseStatusCode = code;
} }
/// <summary>
/// Create an error result
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static WebCallResult<T> CreateErrorResult(Error error) public static WebCallResult<T> CreateErrorResult(Error error)
{ {
return new WebCallResult<T>(null, null, default(T), error); return new WebCallResult<T>(null, null, default(T), error);
} }
/// <summary>
/// Create an error result
/// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="error"></param>
/// <returns></returns>
public static WebCallResult<T> CreateErrorResult(HttpStatusCode? code, IEnumerable<Tuple<string, string>> responseHeaders, Error error) public static WebCallResult<T> CreateErrorResult(HttpStatusCode? code, IEnumerable<Tuple<string, string>> responseHeaders, Error error)
{ {
return new WebCallResult<T>(code, responseHeaders, default(T), error); return new WebCallResult<T>(code, responseHeaders, default(T), error);

View File

@ -1,13 +1,34 @@
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
/// <summary>
/// Constants
/// </summary>
public class Constants public class Constants
{ {
/// <summary>
/// GET Http method
/// </summary>
public const string GetMethod = "GET"; public const string GetMethod = "GET";
/// <summary>
/// POST Http method
/// </summary>
public const string PostMethod = "POST"; public const string PostMethod = "POST";
/// <summary>
/// DELETE Http method
/// </summary>
public const string DeleteMethod = "DELETE"; public const string DeleteMethod = "DELETE";
/// <summary>
/// PUT Http method
/// </summary>
public const string PutMethod = "PUT"; public const string PutMethod = "PUT";
/// <summary>
/// Json content type header
/// </summary>
public const string JsonContentHeader = "application/json"; public const string JsonContentHeader = "application/json";
/// <summary>
/// Form content type header
/// </summary>
public const string FormContentHeader = "application/x-www-form-urlencoded"; public const string FormContentHeader = "application/x-www-form-urlencoded";
} }
} }

View File

@ -1,34 +1,85 @@
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
/// <summary>
/// What to do when a request would exceed the rate limit
/// </summary>
public enum RateLimitingBehaviour public enum RateLimitingBehaviour
{ {
/// <summary>
/// Fail the request
/// </summary>
Fail, Fail,
/// <summary>
/// Wait till the request can be send
/// </summary>
Wait Wait
} }
/// <summary>
/// Where the post parameters should be added
/// </summary>
public enum PostParameters public enum PostParameters
{ {
/// <summary>
/// Post parameters in body
/// </summary>
InBody, InBody,
/// <summary>
/// Post parameters in url
/// </summary>
InUri InUri
} }
/// <summary>
/// The format of the request body
/// </summary>
public enum RequestBodyFormat public enum RequestBodyFormat
{ {
/// <summary>
/// Form data
/// </summary>
FormData, FormData,
/// <summary>
/// Json
/// </summary>
Json Json
} }
/// <summary>
/// Status of the order book
/// </summary>
public enum OrderBookStatus public enum OrderBookStatus
{ {
/// <summary>
/// Not connected
/// </summary>
Disconnected, Disconnected,
/// <summary>
/// Connecting
/// </summary>
Connecting, Connecting,
/// <summary>
/// Syncing data
/// </summary>
Syncing, Syncing,
/// <summary>
/// Data synced, order book is up to date
/// </summary>
Synced, Synced,
} }
/// <summary>
/// Order book entry type
/// </summary>
public enum OrderBookEntryType public enum OrderBookEntryType
{ {
/// <summary>
/// Ask
/// </summary>
Ask, Ask,
/// <summary>
/// Bid
/// </summary>
Bid Bid
} }
} }

View File

@ -1,5 +1,8 @@
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
/// <summary>
/// Base class for errors
/// </summary>
public abstract class Error public abstract class Error
{ {
/// <summary> /// <summary>
@ -11,59 +14,127 @@
/// </summary> /// </summary>
public string Message { get; set; } public string Message { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="code"></param>
/// <param name="message"></param>
protected Error(int code, string message) protected Error(int code, string message)
{ {
Code = code; Code = code;
Message = message; Message = message;
} }
/// <summary>
/// String representation
/// </summary>
/// <returns></returns>
public override string ToString() public override string ToString()
{ {
return $"{Code}: {Message}"; return $"{Code}: {Message}";
} }
} }
/// <summary>
/// Cant reach server error
/// </summary>
public class CantConnectError : Error public class CantConnectError : Error
{ {
/// <summary>
/// ctor
/// </summary>
public CantConnectError() : base(1, "Can't connect to the server") { } public CantConnectError() : base(1, "Can't connect to the server") { }
} }
/// <summary>
/// No api credentials provided while trying to access private endpoint
/// </summary>
public class NoApiCredentialsError : Error public class NoApiCredentialsError : Error
{ {
/// <summary>
/// ctor
/// </summary>
public NoApiCredentialsError() : base(2, "No credentials provided for private endpoint") { } public NoApiCredentialsError() : base(2, "No credentials provided for private endpoint") { }
} }
/// <summary>
/// Error returned by the server
/// </summary>
public class ServerError: Error public class ServerError: Error
{ {
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
public ServerError(string message) : base(3, "Server error: " + message) { } public ServerError(string message) : base(3, "Server error: " + message) { }
/// <summary>
/// ctor
/// </summary>
/// <param name="code"></param>
/// <param name="message"></param>
public ServerError(int code, string message) : base(code, message) public ServerError(int code, string message) : base(code, message)
{ {
} }
} }
/// <summary>
/// Web error returned by the server
/// </summary>
public class WebError : Error public class WebError : Error
{ {
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
public WebError(string message) : base(4, "Web error: " + message) { } public WebError(string message) : base(4, "Web error: " + message) { }
} }
/// <summary>
/// Error while deserializing data
/// </summary>
public class DeserializeError : Error public class DeserializeError : Error
{ {
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
public DeserializeError(string message) : base(5, "Error deserializing data: " + message) { } public DeserializeError(string message) : base(5, "Error deserializing data: " + message) { }
} }
/// <summary>
/// Unknown error
/// </summary>
public class UnknownError : Error public class UnknownError : Error
{ {
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
public UnknownError(string message) : base(6, "Unknown error occured " + message) { } public UnknownError(string message) : base(6, "Unknown error occured " + message) { }
} }
/// <summary>
/// An invalid parameter has been provided
/// </summary>
public class ArgumentError : Error public class ArgumentError : Error
{ {
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
public ArgumentError(string message) : base(7, "Invalid parameter: " + message) { } public ArgumentError(string message) : base(7, "Invalid parameter: " + message) { }
} }
/// <summary>
/// Rate limit exceeded
/// </summary>
public class RateLimitError: Error public class RateLimitError: Error
{ {
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message) { } public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message) { }
} }
} }

View File

@ -91,6 +91,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30); public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>
/// Create a copy of the options
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Copy<T>() where T:RestClientOptions, new() public T Copy<T>() where T:RestClientOptions, new()
{ {
var copy = new T var copy = new T
@ -141,6 +146,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public int? SocketSubscriptionsCombineTarget { get; set; } public int? SocketSubscriptionsCombineTarget { get; set; }
/// <summary>
/// Create a copy of the options
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Copy<T>() where T : SocketClientOptions, new() public T Copy<T>() where T : SocketClientOptions, new()
{ {
var copy = new T var copy = new T

View File

@ -1,5 +1,8 @@
namespace CryptoExchange.Net.OrderBook namespace CryptoExchange.Net.OrderBook
{ {
/// <summary>
/// Interface for order book entries
/// </summary>
public interface ISymbolOrderBookEntry public interface ISymbolOrderBookEntry
{ {
/// <summary> /// <summary>

View File

@ -1,10 +1,24 @@
namespace CryptoExchange.Net.OrderBook namespace CryptoExchange.Net.OrderBook
{ {
/// <summary>
/// Order book entry
/// </summary>
public class OrderBookEntry : ISymbolOrderBookEntry public class OrderBookEntry : ISymbolOrderBookEntry
{ {
/// <summary>
/// Quantity of the entry
/// </summary>
public decimal Quantity { get; set; } public decimal Quantity { get; set; }
/// <summary>
/// Price of the entry
/// </summary>
public decimal Price { get; set; } public decimal Price { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="price"></param>
/// <param name="quantity"></param>
public OrderBookEntry(decimal price, decimal quantity) public OrderBookEntry(decimal price, decimal quantity)
{ {
Quantity = quantity; Quantity = quantity;

View File

@ -2,12 +2,27 @@
namespace CryptoExchange.Net.OrderBook namespace CryptoExchange.Net.OrderBook
{ {
/// <summary>
/// Buffer entry for order book
/// </summary>
public class ProcessBufferEntry public class ProcessBufferEntry
{ {
/// <summary>
/// The first sequence number of the entries
/// </summary>
public long FirstSequence { get; set; } public long FirstSequence { get; set; }
/// <summary>
/// The last sequence number of the entries
/// </summary>
public long LastSequence { get; set; } public long LastSequence { get; set; }
/// <summary>
/// List of entries
/// </summary>
public List<ProcessEntry> Entries { get; set; } public List<ProcessEntry> Entries { get; set; }
/// <summary>
/// ctor
/// </summary>
public ProcessBufferEntry() public ProcessBufferEntry()
{ {
Entries = new List<ProcessEntry>(); Entries = new List<ProcessEntry>();

View File

@ -2,11 +2,25 @@
namespace CryptoExchange.Net.OrderBook namespace CryptoExchange.Net.OrderBook
{ {
/// <summary>
/// Process entry for order book
/// </summary>
public class ProcessEntry public class ProcessEntry
{ {
/// <summary>
/// The entry
/// </summary>
public ISymbolOrderBookEntry Entry { get; set; } public ISymbolOrderBookEntry Entry { get; set; }
/// <summary>
/// The type
/// </summary>
public OrderBookEntryType Type { get; set; } public OrderBookEntryType Type { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="type"></param>
/// <param name="entry"></param>
public ProcessEntry(OrderBookEntryType type, ISymbolOrderBookEntry entry) public ProcessEntry(OrderBookEntryType type, ISymbolOrderBookEntry entry)
{ {
Type = type; Type = type;

View File

@ -15,14 +15,26 @@ namespace CryptoExchange.Net.OrderBook
/// </summary> /// </summary>
public abstract class SymbolOrderBook: IDisposable public abstract class SymbolOrderBook: IDisposable
{ {
/// <summary>
/// The process buffer, used while syncing
/// </summary>
protected readonly List<ProcessBufferEntry> processBuffer; protected readonly List<ProcessBufferEntry> processBuffer;
private readonly object bookLock = new object(); private readonly object bookLock = new object();
/// <summary>
/// The ask list
/// </summary>
protected SortedList<decimal, OrderBookEntry> asks; protected SortedList<decimal, OrderBookEntry> asks;
/// <summary>
/// The bid list
/// </summary>
protected SortedList<decimal, OrderBookEntry> bids; protected SortedList<decimal, OrderBookEntry> bids;
private OrderBookStatus status; private OrderBookStatus status;
private UpdateSubscription subscription; private UpdateSubscription subscription;
private readonly bool sequencesAreConsecutive; private readonly bool sequencesAreConsecutive;
private readonly string id; private readonly string id;
/// <summary>
/// The log
/// </summary>
protected Log log; protected Log log;
private bool bookSet; private bool bookSet;
@ -116,6 +128,11 @@ namespace CryptoExchange.Net.OrderBook
} }
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="symbol"></param>
/// <param name="options"></param>
protected SymbolOrderBook(string symbol, OrderBookOptions options) protected SymbolOrderBook(string symbol, OrderBookOptions options)
{ {
id = options.OrderBookName; id = options.OrderBookName;
@ -198,12 +215,29 @@ namespace CryptoExchange.Net.OrderBook
await subscription.Close().ConfigureAwait(false); await subscription.Close().ConfigureAwait(false);
} }
/// <summary>
/// Start the order book
/// </summary>
/// <returns></returns>
protected abstract Task<CallResult<UpdateSubscription>> DoStart(); protected abstract Task<CallResult<UpdateSubscription>> DoStart();
/// <summary>
/// Reset the order book
/// </summary>
protected virtual void DoReset() { } protected virtual void DoReset() { }
/// <summary>
/// Resync the order book
/// </summary>
/// <returns></returns>
protected abstract Task<CallResult<bool>> DoResync(); protected abstract Task<CallResult<bool>> DoResync();
/// <summary>
/// Set the initial data for the order book
/// </summary>
/// <param name="orderBookSequenceNumber">The last update sequence number</param>
/// <param name="askList">List of asks</param>
/// <param name="bidList">List of bids</param>
protected void SetInitialOrderBook(long orderBookSequenceNumber, IEnumerable<ISymbolOrderBookEntry> askList, IEnumerable<ISymbolOrderBookEntry> bidList) protected void SetInitialOrderBook(long orderBookSequenceNumber, IEnumerable<ISymbolOrderBookEntry> askList, IEnumerable<ISymbolOrderBookEntry> bidList)
{ {
lock (bookLock) lock (bookLock)
@ -229,6 +263,12 @@ namespace CryptoExchange.Net.OrderBook
} }
} }
/// <summary>
/// Update the order book with entries
/// </summary>
/// <param name="firstSequenceNumber">First sequence number</param>
/// <param name="lastSequenceNumber">Last sequence number</param>
/// <param name="entries">List of entries</param>
protected void UpdateOrderBook(long firstSequenceNumber, long lastSequenceNumber, List<ProcessEntry> entries) protected void UpdateOrderBook(long firstSequenceNumber, long lastSequenceNumber, List<ProcessEntry> entries)
{ {
lock (bookLock) lock (bookLock)
@ -264,6 +304,9 @@ namespace CryptoExchange.Net.OrderBook
} }
} }
/// <summary>
/// Check and empty the process buffer; see what entries to update the book with
/// </summary>
protected void CheckProcessBuffer() protected void CheckProcessBuffer()
{ {
foreach (var bufferEntry in processBuffer.OrderBy(b => b.FirstSequence).ToList()) foreach (var bufferEntry in processBuffer.OrderBy(b => b.FirstSequence).ToList())
@ -284,6 +327,11 @@ namespace CryptoExchange.Net.OrderBook
} }
} }
/// <summary>
/// Update order book with an entry
/// </summary>
/// <param name="type">Type of entry</param>
/// <param name="entry">The entry</param>
protected virtual void ProcessUpdate(OrderBookEntryType type, ISymbolOrderBookEntry entry) protected virtual void ProcessUpdate(OrderBookEntryType type, ISymbolOrderBookEntry entry)
{ {
var listToChange = type == OrderBookEntryType.Ask ? asks : bids; var listToChange = type == OrderBookEntryType.Ask ? asks : bids;
@ -311,13 +359,24 @@ namespace CryptoExchange.Net.OrderBook
} }
} }
/// <summary>
/// Dispose the order book
/// </summary>
public abstract void Dispose(); public abstract void Dispose();
/// <summary>
/// String representation of the top 3 entries
/// </summary>
/// <returns></returns>
public override string ToString() public override string ToString()
{ {
return ToString(3); return ToString(3);
} }
/// <summary>
/// String representation of the top x entries
/// </summary>
/// <returns></returns>
public string ToString(int numberOfEntries) public string ToString(int numberOfEntries)
{ {
var result = ""; var result = "";

View File

@ -4,17 +4,33 @@ using System.Linq;
namespace CryptoExchange.Net.RateLimiter namespace CryptoExchange.Net.RateLimiter
{ {
/// <summary>
/// Rate limiting object
/// </summary>
public class RateLimitObject public class RateLimitObject
{ {
/// <summary>
/// Lock
/// </summary>
public object LockObject { get; } public object LockObject { get; }
private List<DateTime> Times { get; } private List<DateTime> Times { get; }
/// <summary>
/// ctor
/// </summary>
public RateLimitObject() public RateLimitObject()
{ {
LockObject = new object(); LockObject = new object();
Times = new List<DateTime>(); Times = new List<DateTime>();
} }
/// <summary>
/// Get time to wait for a specific time
/// </summary>
/// <param name="time"></param>
/// <param name="limit"></param>
/// <param name="perTimePeriod"></param>
/// <returns></returns>
public int GetWaitTime(DateTime time, int limit, TimeSpan perTimePeriod) public int GetWaitTime(DateTime time, int limit, TimeSpan perTimePeriod)
{ {
Times.RemoveAll(d => d < time - perTimePeriod); Times.RemoveAll(d => d < time - perTimePeriod);
@ -23,6 +39,10 @@ namespace CryptoExchange.Net.RateLimiter
return 0; return 0;
} }
/// <summary>
/// Add an executed request time
/// </summary>
/// <param name="time"></param>
public void Add(DateTime time) public void Add(DateTime time)
{ {
Times.Add(time); Times.Add(time);

View File

@ -29,7 +29,7 @@ namespace CryptoExchange.Net.RateLimiter
this.perTimePeriod = perTimePeriod; this.perTimePeriod = perTimePeriod;
} }
/// <inheritdoc />
public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour) public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour)
{ {
if(client.authProvider?.Credentials == null) if(client.authProvider?.Credentials == null)

View File

@ -29,6 +29,7 @@ namespace CryptoExchange.Net.RateLimiter
this.perTimePeriod = perTimePeriod; this.perTimePeriod = perTimePeriod;
} }
/// <inheritdoc />
public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitingBehaviour) public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitingBehaviour)
{ {
int waitTime; int waitTime;

View File

@ -30,6 +30,7 @@ namespace CryptoExchange.Net.RateLimiter
this.perTimePeriod = perTimePeriod; this.perTimePeriod = perTimePeriod;
} }
/// <inheritdoc />
public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour) public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour)
{ {
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();

View File

@ -6,65 +6,84 @@ using CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.Requests namespace CryptoExchange.Net.Requests
{ {
/// <summary>
/// Request object
/// </summary>
public class Request : IRequest public class Request : IRequest
{ {
private readonly WebRequest request; private readonly WebRequest request;
/// <summary>
/// Create request object for webrequest
/// </summary>
/// <param name="request"></param>
public Request(WebRequest request) public Request(WebRequest request)
{ {
this.request = request; this.request = request;
} }
/// <inheritdoc />
public WebHeaderCollection Headers public WebHeaderCollection Headers
{ {
get => request.Headers; get => request.Headers;
set => request.Headers = value; set => request.Headers = value;
} }
/// <inheritdoc />
public string ContentType public string ContentType
{ {
get => request.ContentType; get => request.ContentType;
set => request.ContentType = value; set => request.ContentType = value;
} }
/// <inheritdoc />
public string Content { get; set; } public string Content { get; set; }
/// <inheritdoc />
public string Accept public string Accept
{ {
get => ((HttpWebRequest)request).Accept; get => ((HttpWebRequest)request).Accept;
set => ((HttpWebRequest)request).Accept = value; set => ((HttpWebRequest)request).Accept = value;
} }
/// <inheritdoc />
public long ContentLength public long ContentLength
{ {
get => ((HttpWebRequest)request).ContentLength; get => ((HttpWebRequest)request).ContentLength;
set => ((HttpWebRequest)request).ContentLength = value; set => ((HttpWebRequest)request).ContentLength = value;
} }
/// <inheritdoc />
public string Method public string Method
{ {
get => request.Method; get => request.Method;
set => request.Method = value; set => request.Method = value;
} }
/// <inheritdoc />
public TimeSpan Timeout public TimeSpan Timeout
{ {
get => TimeSpan.FromMilliseconds(request.Timeout); get => TimeSpan.FromMilliseconds(request.Timeout);
set => request.Timeout = (int)Math.Round(value.TotalMilliseconds); set => request.Timeout = (int)Math.Round(value.TotalMilliseconds);
} }
/// <inheritdoc />
public Uri Uri => request.RequestUri; public Uri Uri => request.RequestUri;
/// <inheritdoc />
public void SetProxy(string host, int port, string login, string password) public void SetProxy(string host, int port, string login, string password)
{ {
request.Proxy = new WebProxy(host, port); request.Proxy = new WebProxy(host, port);
if(!string.IsNullOrEmpty(login) && !string.IsNullOrEmpty(password)) request.Proxy.Credentials = new NetworkCredential(login, password); if(!string.IsNullOrEmpty(login) && !string.IsNullOrEmpty(password)) request.Proxy.Credentials = new NetworkCredential(login, password);
} }
/// <inheritdoc />
public async Task<Stream> GetRequestStream() public async Task<Stream> GetRequestStream()
{ {
return await request.GetRequestStreamAsync().ConfigureAwait(false); return await request.GetRequestStreamAsync().ConfigureAwait(false);
} }
/// <inheritdoc />
public async Task<IResponse> GetResponse() public async Task<IResponse> GetResponse()
{ {
return new Response((HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false)); return new Response((HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false));

View File

@ -3,8 +3,12 @@ using CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.Requests namespace CryptoExchange.Net.Requests
{ {
/// <summary>
/// WebRequest factory
/// </summary>
public class RequestFactory : IRequestFactory public class RequestFactory : IRequestFactory
{ {
/// <inheritdoc />
public IRequest Create(string uri) public IRequest Create(string uri)
{ {
return new Request(WebRequest.Create(uri)); return new Request(WebRequest.Create(uri));

View File

@ -1,33 +1,43 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.Requests namespace CryptoExchange.Net.Requests
{ {
/// <summary>
/// HttpWebResponse response object
/// </summary>
public class Response : IResponse public class Response : IResponse
{ {
private readonly HttpWebResponse response; private readonly HttpWebResponse response;
/// <inheritdoc />
public HttpStatusCode StatusCode => response.StatusCode; public HttpStatusCode StatusCode => response.StatusCode;
/// <summary>
/// Create response for http web response
/// </summary>
/// <param name="response"></param>
public Response(HttpWebResponse response) public Response(HttpWebResponse response)
{ {
this.response = response; this.response = response;
} }
/// <inheritdoc />
public Stream GetResponseStream() public Stream GetResponseStream()
{ {
return response.GetResponseStream(); return response.GetResponseStream();
} }
/// <inheritdoc />
public IEnumerable<Tuple<string, string>> GetResponseHeaders() public IEnumerable<Tuple<string, string>> GetResponseHeaders()
{ {
return response.Headers.ToIEnumerable(); return response.Headers.ToIEnumerable();
} }
/// <inheritdoc />
public void Close() public void Close()
{ {
response.Close(); response.Close();

View File

@ -19,6 +19,9 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
/// <summary>
/// Base rest client
/// </summary>
public abstract class RestClient: BaseClient, IRestClient public abstract class RestClient: BaseClient, IRestClient
{ {
/// <summary> /// <summary>
@ -27,14 +30,37 @@ namespace CryptoExchange.Net
public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
/// <summary>
/// Where to place post parameters
/// </summary>
protected PostParameters postParametersPosition = PostParameters.InBody; protected PostParameters postParametersPosition = PostParameters.InBody;
/// <summary>
/// Request body content type
/// </summary>
protected RequestBodyFormat requestBodyFormat = RequestBodyFormat.Json; protected RequestBodyFormat requestBodyFormat = RequestBodyFormat.Json;
/// <summary>
/// Timeout for requests
/// </summary>
protected TimeSpan RequestTimeout { get; private set; } protected TimeSpan RequestTimeout { get; private set; }
/// <summary>
/// Rate limiting behaviour
/// </summary>
public RateLimitingBehaviour RateLimitBehaviour { get; private set; } public RateLimitingBehaviour RateLimitBehaviour { get; private set; }
/// <summary>
/// List of ratelimitters
/// </summary>
public IEnumerable<IRateLimiter> RateLimiters { get; private set; } public IEnumerable<IRateLimiter> RateLimiters { get; private set; }
/// <summary>
/// Total requests made
/// </summary>
public int TotalRequestsMade { get; private set; } public int TotalRequestsMade { get; private set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="exchangeOptions"></param>
/// <param name="authenticationProvider"></param>
protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider) protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider)
{ {
Configure(exchangeOptions); Configure(exchangeOptions);

View File

@ -13,6 +13,9 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
/// <summary>
/// Base for socket client implementations
/// </summary>
public abstract class SocketClient: BaseClient, ISocketClient public abstract class SocketClient: BaseClient, ISocketClient
{ {
#region fields #region fields
@ -25,6 +28,8 @@ namespace CryptoExchange.Net
/// List of socket connections currently connecting/connected /// List of socket connections currently connecting/connected
/// </summary> /// </summary>
protected internal ConcurrentDictionary<int, SocketConnection> sockets = new ConcurrentDictionary<int, SocketConnection>(); protected internal ConcurrentDictionary<int, SocketConnection> sockets = new ConcurrentDictionary<int, SocketConnection>();
/// <summary>
/// </summary>
protected internal readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1); protected internal readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
/// <inheritdoc cref="SocketClientOptions.ReconnectInterval"/> /// <inheritdoc cref="SocketClientOptions.ReconnectInterval"/>
@ -42,14 +47,43 @@ namespace CryptoExchange.Net
/// <inheritdoc cref="SocketClientOptions.SocketSubscriptionsCombineTarget"/> /// <inheritdoc cref="SocketClientOptions.SocketSubscriptionsCombineTarget"/>
public int SocketCombineTarget { get; protected set; } public int SocketCombineTarget { get; protected set; }
/// <summary>
/// Handler for byte data
/// </summary>
protected Func<byte[], string> dataInterpreterBytes; protected Func<byte[], string> dataInterpreterBytes;
/// <summary>
/// Handler for string data
/// </summary>
protected Func<string, string> dataInterpreterString; protected Func<string, string> dataInterpreterString;
/// <summary>
/// Generic handlers
/// </summary>
protected Dictionary<string, Action<SocketConnection, JToken>> genericHandlers = new Dictionary<string, Action<SocketConnection, JToken>>(); protected Dictionary<string, Action<SocketConnection, JToken>> genericHandlers = new Dictionary<string, Action<SocketConnection, JToken>>();
/// <summary>
/// Periodic task
/// </summary>
protected Task periodicTask; protected Task periodicTask;
/// <summary>
/// Periodic task event
/// </summary>
protected AutoResetEvent periodicEvent; protected AutoResetEvent periodicEvent;
/// <summary>
/// Is disposing
/// </summary>
protected bool disposing; protected bool disposing;
/// <summary>
/// If true; data which is a response to a query will also be distributed to subscriptions
/// If false; data which is a response to a query won't get forwarded to subscriptions as well
/// </summary>
protected internal bool ContinueOnQueryResponse { get; protected set; }
#endregion #endregion
/// <summary>
/// Create a socket client
/// </summary>
/// <param name="exchangeOptions">Client options</param>
/// <param name="authenticationProvider">Authentication provider</param>
protected SocketClient(SocketClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider) protected SocketClient(SocketClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider)
{ {
Configure(exchangeOptions); Configure(exchangeOptions);
@ -174,6 +208,13 @@ namespace CryptoExchange.Net
return new CallResult<bool>(callResult?.Success ?? false, callResult == null ? new ServerError("No response on subscription request received"): callResult.Error); return new CallResult<bool>(callResult?.Success ?? false, callResult == null ? new ServerError("No response on subscription request received"): callResult.Error);
} }
/// <summary>
/// Query for data
/// </summary>
/// <typeparam name="T">Exepected result type</typeparam>
/// <param name="request">The request to send</param>
/// <param name="authenticated">Whether the socket should be authenticated</param>
/// <returns></returns>
protected virtual Task<CallResult<T>> Query<T>(object request, bool authenticated) protected virtual Task<CallResult<T>> Query<T>(object request, bool authenticated)
{ {
return Query<T>(BaseAddress, request, authenticated); return Query<T>(BaseAddress, request, authenticated);
@ -183,6 +224,7 @@ namespace CryptoExchange.Net
/// Query for data /// Query for data
/// </summary> /// </summary>
/// <typeparam name="T">The expected result type</typeparam> /// <typeparam name="T">The expected result type</typeparam>
/// <param name="url">The url for the request</param>
/// <param name="request">The request to send</param> /// <param name="request">The request to send</param>
/// <param name="authenticated">Whether the socket should be authenticated</param> /// <param name="authenticated">Whether the socket should be authenticated</param>
/// <returns></returns> /// <returns></returns>
@ -293,7 +335,7 @@ namespace CryptoExchange.Net
/// <param name="s">The socket connection</param> /// <param name="s">The socket connection</param>
/// <param name="subscription"></param> /// <param name="subscription"></param>
/// <param name="request">The request that a response is awaited for</param> /// <param name="request">The request that a response is awaited for</param>
/// <param name="data">The message</param> /// <param name="message">The message</param>
/// <param name="callResult">The interpretation (null if message wasn't a response to the request)</param> /// <param name="callResult">The interpretation (null if message wasn't a response to the request)</param>
/// <returns>True if the message was a response to the subscription request</returns> /// <returns>True if the message was a response to the subscription request</returns>
protected internal abstract bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken message, out CallResult<object> callResult); protected internal abstract bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken message, out CallResult<object> callResult);

View File

@ -13,7 +13,10 @@ using WebSocket4Net;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
public class BaseSocket: IWebsocket /// <summary>
/// Socket implementation
/// </summary>
internal class BaseSocket: IWebsocket
{ {
internal static int lastStreamId; internal static int lastStreamId;
private static readonly object streamIdLock = new object(); private static readonly object streamIdLock = new object();

View File

@ -11,25 +11,58 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
/// <summary>
/// Socket connecting
/// </summary>
public class SocketConnection public class SocketConnection
{ {
/// <summary>
/// Connection lost event
/// </summary>
public event Action ConnectionLost; public event Action ConnectionLost;
/// <summary>
/// Connecting restored event
/// </summary>
public event Action<TimeSpan> ConnectionRestored; public event Action<TimeSpan> ConnectionRestored;
/// <summary>
/// Connecting closed event
/// </summary>
public event Action Closed; public event Action Closed;
/// <summary>
/// The amount of handlers
/// </summary>
public int HandlerCount public int HandlerCount
{ {
get { lock (handlersLock) get { lock (handlersLock)
return handlers.Count(h => h.UserSubscription); } return handlers.Count(h => h.UserSubscription); }
} }
/// <summary>
/// If connection is authenticated
/// </summary>
public bool Authenticated { get; set; } public bool Authenticated { get; set; }
/// <summary>
/// If connection is made
/// </summary>
public bool Connected { get; private set; } public bool Connected { get; private set; }
/// <summary>
/// The socket
/// </summary>
public IWebsocket Socket { get; set; } public IWebsocket Socket { get; set; }
/// <summary>
/// If should reconnect upon closing
/// </summary>
public bool ShouldReconnect { get; set; } public bool ShouldReconnect { get; set; }
/// <summary>
/// Time of disconnecting
/// </summary>
public DateTime? DisconnectTime { get; set; } public DateTime? DisconnectTime { get; set; }
/// <summary>
/// If activity is paused
/// </summary>
public bool PausedActivity { get; set; } public bool PausedActivity { get; set; }
internal readonly List<SocketSubscription> handlers; internal readonly List<SocketSubscription> handlers;
@ -41,6 +74,11 @@ namespace CryptoExchange.Net.Sockets
private readonly List<PendingRequest> pendingRequests; private readonly List<PendingRequest> pendingRequests;
/// <summary>
/// New socket connection
/// </summary>
/// <param name="client">The socket client</param>
/// <param name="socket">The socket</param>
public SocketConnection(SocketClient client, IWebsocket socket) public SocketConnection(SocketClient client, IWebsocket socket)
{ {
log = client.log; log = client.log;
@ -72,6 +110,13 @@ namespace CryptoExchange.Net.Sockets
}; };
} }
/// <summary>
/// Add a handler
/// </summary>
/// <param name="request">The request object</param>
/// <param name="userSubscription">If it is a user subscription or a generic handler</param>
/// <param name="dataHandler">The data handler</param>
/// <returns></returns>
public SocketSubscription AddHandler(object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler) public SocketSubscription AddHandler(object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{ {
var handler = new SocketSubscription(null, request, userSubscription, dataHandler); var handler = new SocketSubscription(null, request, userSubscription, dataHandler);
@ -80,6 +125,14 @@ namespace CryptoExchange.Net.Sockets
return handler; return handler;
} }
/// <summary>
/// Add a handler
/// </summary>
/// <param name="identifier">The identifier of the handler</param>
/// <param name="userSubscription">If it is a user subscription or a generic handler</param>
/// <param name="dataHandler">The data handler</param>
/// <returns></returns>
/// <returns></returns>
public SocketSubscription AddHandler(string identifier, bool userSubscription, Action<SocketConnection, JToken> dataHandler) public SocketSubscription AddHandler(string identifier, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{ {
var handler = new SocketSubscription(identifier, null, userSubscription, dataHandler); var handler = new SocketSubscription(identifier, null, userSubscription, dataHandler);
@ -88,7 +141,7 @@ namespace CryptoExchange.Net.Sockets
return handler; return handler;
} }
public void ProcessMessage(string data) private void ProcessMessage(string data)
{ {
log.Write(LogVerbosity.Debug, $"Socket {Socket.Id} received data: " + data); log.Write(LogVerbosity.Debug, $"Socket {Socket.Id} received data: " + data);
var tokenData = data.ToJToken(log); var tokenData = data.ToJToken(log);
@ -100,7 +153,9 @@ namespace CryptoExchange.Net.Sockets
if (pendingRequest.Check(tokenData)) if (pendingRequest.Check(tokenData))
{ {
pendingRequests.Remove(pendingRequest); pendingRequests.Remove(pendingRequest);
return; if (!socketClient.ContinueOnQueryResponse)
return;
break;
} }
} }
@ -156,6 +211,14 @@ namespace CryptoExchange.Net.Sockets
} }
} }
/// <summary>
/// Send data
/// </summary>
/// <typeparam name="T">The data type</typeparam>
/// <param name="obj">The object to send</param>
/// <param name="timeout">The timeout for response</param>
/// <param name="handler">The response handler</param>
/// <returns></returns>
public virtual Task SendAndWait<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler) public virtual Task SendAndWait<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler)
{ {
var pending = new PendingRequest(handler, timeout); var pending = new PendingRequest(handler, timeout);
@ -230,7 +293,7 @@ namespace CryptoExchange.Net.Sockets
if (lostTriggered) if (lostTriggered)
{ {
lostTriggered = false; lostTriggered = false;
Task.Run(() => ConnectionRestored?.Invoke(DisconnectTime.HasValue ? DateTime.UtcNow - DisconnectTime.Value : TimeSpan.FromSeconds(0))); InvokeConnectionRestored();
} }
break; break;
@ -248,7 +311,12 @@ namespace CryptoExchange.Net.Sockets
} }
} }
public async Task<bool> ProcessReconnect() private async void InvokeConnectionRestored()
{
await Task.Run(() => ConnectionRestored?.Invoke(DisconnectTime.HasValue ? DateTime.UtcNow - DisconnectTime.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
}
private async Task<bool> ProcessReconnect()
{ {
if (Authenticated) if (Authenticated)
{ {
@ -279,6 +347,10 @@ namespace CryptoExchange.Net.Sockets
return true; return true;
} }
/// <summary>
/// Close the connection
/// </summary>
/// <returns></returns>
public async Task Close() public async Task Close()
{ {
Connected = false; Connected = false;
@ -290,6 +362,11 @@ namespace CryptoExchange.Net.Sockets
Socket.Dispose(); Socket.Dispose();
} }
/// <summary>
/// Close the subscriptions
/// </summary>
/// <param name="subscription">Subscription to close</param>
/// <returns></returns>
public async Task Close(SocketSubscription subscription) public async Task Close(SocketSubscription subscription)
{ {
if (subscription.Confirmed) if (subscription.Confirmed)
@ -308,7 +385,7 @@ namespace CryptoExchange.Net.Sockets
} }
} }
public class PendingRequest internal class PendingRequest
{ {
public Func<JToken, bool> Handler { get; } public Func<JToken, bool> Handler { get; }
public JToken Result { get; private set; } public JToken Result { get; private set; }

View File

@ -3,8 +3,14 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
/// <summary>
/// Socket subscription
/// </summary>
public class SocketSubscription public class SocketSubscription
{ {
/// <summary>
/// Exception event
/// </summary>
public event Action<Exception> Exception; public event Action<Exception> Exception;
/// <summary> /// <summary>
@ -12,13 +18,32 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public Action<SocketConnection, JToken> MessageHandler { get; set; } public Action<SocketConnection, JToken> MessageHandler { get; set; }
/// <summary>
/// Request object
/// </summary>
public object Request { get; set; } public object Request { get; set; }
/// <summary>
/// Subscription identifier
/// </summary>
public string Identifier { get; set; } public string Identifier { get; set; }
/// <summary>
/// Is user subscription or generic
/// </summary>
public bool UserSubscription { get; set; } public bool UserSubscription { get; set; }
/// <summary>
/// If the subscription has been confirmed
/// </summary>
public bool Confirmed { get; set; } public bool Confirmed { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="identifier"></param>
/// <param name="request"></param>
/// <param name="userSubscription"></param>
/// <param name="dataHandler"></param>
public SocketSubscription(string identifier, object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler) public SocketSubscription(string identifier, object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{ {
UserSubscription = userSubscription; UserSubscription = userSubscription;
@ -27,6 +52,10 @@ namespace CryptoExchange.Net.Sockets
Request = request; Request = request;
} }
/// <summary>
/// Invoke the exception event
/// </summary>
/// <param name="e"></param>
public void InvokeExceptionHandler(Exception e) public void InvokeExceptionHandler(Exception e)
{ {
Exception?.Invoke(e); Exception?.Invoke(e);

View File

@ -3,6 +3,9 @@ using System.Threading.Tasks;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
/// <summary>
/// Subscription
/// </summary>
public class UpdateSubscription public class UpdateSubscription
{ {
private readonly SocketConnection connection; private readonly SocketConnection connection;
@ -40,6 +43,11 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public int Id => connection.Socket.Id; public int Id => connection.Socket.Id;
/// <summary>
/// ctor
/// </summary>
/// <param name="connection"></param>
/// <param name="subscription"></param>
public UpdateSubscription(SocketConnection connection, SocketSubscription subscription) public UpdateSubscription(SocketConnection connection, SocketSubscription subscription)
{ {
this.connection = connection; this.connection = connection;

View File

@ -4,13 +4,18 @@ using CryptoExchange.Net.Logging;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
/// <summary>
/// Factory implementation
/// </summary>
public class WebsocketFactory : IWebsocketFactory public class WebsocketFactory : IWebsocketFactory
{ {
/// <inheritdoc />
public IWebsocket CreateWebsocket(Log log, string url) public IWebsocket CreateWebsocket(Log log, string url)
{ {
return new BaseSocket(log, url); return new BaseSocket(log, url);
} }
/// <inheritdoc />
public IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers) public IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers)
{ {
return new BaseSocket(log, url, cookies, headers); return new BaseSocket(log, url, cookies, headers);

View File

@ -190,6 +190,9 @@ The order book will automatically reconnect when the connection is lost and resy
To stop synchronizing an order book use the `Stop` method. To stop synchronizing an order book use the `Stop` method.
## Release notes ## Release notes
* Version 2.1.6 - 06 Aug 2019
* Fix for missing subscription events if they are also a request response, added code docs
* Version 2.1.5 - 09 jul 2019 * Version 2.1.5 - 09 jul 2019
* Updated SymbolOrderBook * Updated SymbolOrderBook