1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-10 17:36:19 +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
{
/// <summary>
/// Marks property as optional
/// </summary>
public class JsonOptionalPropertyAttribute : Attribute
{
}

View File

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

View File

@ -2,35 +2,76 @@
namespace CryptoExchange.Net.Authentication
{
/// <summary>
/// Base class for authentication providers
/// </summary>
public abstract class AuthenticationProvider
{
/// <summary>
/// The provided credentials
/// </summary>
public ApiCredentials Credentials { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="credentials"></param>
protected AuthenticationProvider(ApiCredentials 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)
{
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)
{
return new Dictionary<string, string>();
}
/// <summary>
/// Sign a string
/// </summary>
/// <param name="toSign"></param>
/// <returns></returns>
public virtual string Sign(string toSign)
{
return toSign;
}
/// <summary>
/// Sign a byte array
/// </summary>
/// <param name="toSign"></param>
/// <returns></returns>
public virtual byte[] Sign(byte[] toSign)
{
return toSign;
}
/// <summary>
/// Convert byte array to hex
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected string ByteToString(byte[] buff)
{
var result = "";

View File

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

View File

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

View File

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

View File

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

View File

@ -3,13 +3,18 @@ using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters
{
/// <summary>
/// converter for milliseconds to datetime
/// </summary>
public class TimestampConverter : JsonConverter
{
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
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);
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
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
{
/// <summary>
/// Converter for nanoseconds to datetime
/// </summary>
public class TimestampNanoSecondsConverter : JsonConverter
{
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
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));
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var ticksPerNanosecond = (TimeSpan.TicksPerMillisecond / 1000m / 1000);

View File

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

View File

@ -1,27 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<PackageVersion>2.1.5</PackageVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Authors>JKorf</Authors>
<PackageVersion>2.1.6</PackageVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE</PackageLicenseUrl>
<NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseUrl>https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE</PackageLicenseUrl>
<NeutralLanguage>en</NeutralLanguage>
<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 Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
</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
{
/// <summary>
/// Helper methods
/// </summary>
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)
{
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)
{
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)
{
if(value != null)
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)
{
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)
{
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)
{
RegisteredWaitHandle registeredHandle = null;
@ -126,12 +165,24 @@ namespace CryptoExchange.Net
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)
{
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)
{
if (string.IsNullOrEmpty(stringData))

View File

@ -2,8 +2,18 @@
namespace CryptoExchange.Net.Interfaces
{
/// <summary>
/// Rate limiter interface
/// </summary>
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);
}
}

View File

@ -5,20 +5,62 @@ using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
{
/// <summary>
/// Request interface
/// </summary>
public interface IRequest
{
/// <summary>
/// The uri of the request
/// </summary>
Uri Uri { get; }
/// <summary>
/// The headers of the request
/// </summary>
WebHeaderCollection Headers { get; set; }
/// <summary>
/// The method of the request
/// </summary>
string Method { get; set; }
/// <summary>
/// The timeout of the request
/// </summary>
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);
/// <summary>
/// Content type
/// </summary>
string ContentType { get; set; }
/// <summary>
/// String content
/// </summary>
string Content { get; set; }
/// <summary>
/// Accept
/// </summary>
string Accept { get; set; }
/// <summary>
/// Content length
/// </summary>
long ContentLength { get; set; }
/// <summary>
/// Get the request stream
/// </summary>
/// <returns></returns>
Task<Stream> GetRequestStream();
/// <summary>
/// Get the response object
/// </summary>
/// <returns></returns>
Task<IResponse> GetResponse();
}
}

View File

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

View File

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

View File

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

View File

@ -3,9 +3,26 @@ using CryptoExchange.Net.Logging;
namespace CryptoExchange.Net.Interfaces
{
/// <summary>
/// Websocket factory interface
/// </summary>
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);
/// <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);
}
}

View File

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

View File

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

View File

@ -4,6 +4,9 @@ using System.Text;
namespace CryptoExchange.Net.Logging
{
/// <summary>
/// File writer
/// </summary>
public class ThreadSafeFileWriter: TextWriter
{
private static readonly object openedFilesLock = new object();
@ -12,8 +15,13 @@ namespace CryptoExchange.Net.Logging
private StreamWriter logWriter;
private readonly object writeLock;
/// <inheritdoc />
public override Encoding Encoding => Encoding.ASCII;
/// <summary>
/// ctor
/// </summary>
/// <param name="path"></param>
public ThreadSafeFileWriter(string path)
{
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)
{
lock(writeLock)
logWriter.WriteLine(logMessage);
}
/// <summary>
/// Dispose
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
lock (writeLock)

View File

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

View File

@ -3,8 +3,17 @@ using System.Collections.Generic;
namespace CryptoExchange.Net.Objects
{
/// <summary>
/// Comparer for byte order
/// </summary>
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)
{
// Shortcuts: If both are null, they are the same.

View File

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

View File

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

View File

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

View File

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

View File

@ -91,6 +91,11 @@ namespace CryptoExchange.Net.Objects
/// </summary>
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()
{
var copy = new T
@ -141,6 +146,11 @@ namespace CryptoExchange.Net.Objects
/// </summary>
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()
{
var copy = new T

View File

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

View File

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

View File

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

View File

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

View File

@ -15,14 +15,26 @@ namespace CryptoExchange.Net.OrderBook
/// </summary>
public abstract class SymbolOrderBook: IDisposable
{
/// <summary>
/// The process buffer, used while syncing
/// </summary>
protected readonly List<ProcessBufferEntry> processBuffer;
private readonly object bookLock = new object();
/// <summary>
/// The ask list
/// </summary>
protected SortedList<decimal, OrderBookEntry> asks;
/// <summary>
/// The bid list
/// </summary>
protected SortedList<decimal, OrderBookEntry> bids;
private OrderBookStatus status;
private UpdateSubscription subscription;
private readonly bool sequencesAreConsecutive;
private readonly string id;
/// <summary>
/// The log
/// </summary>
protected Log log;
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)
{
id = options.OrderBookName;
@ -198,12 +215,29 @@ namespace CryptoExchange.Net.OrderBook
await subscription.Close().ConfigureAwait(false);
}
/// <summary>
/// Start the order book
/// </summary>
/// <returns></returns>
protected abstract Task<CallResult<UpdateSubscription>> DoStart();
/// <summary>
/// Reset the order book
/// </summary>
protected virtual void DoReset() { }
/// <summary>
/// Resync the order book
/// </summary>
/// <returns></returns>
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)
{
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)
{
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()
{
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)
{
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();
/// <summary>
/// String representation of the top 3 entries
/// </summary>
/// <returns></returns>
public override string ToString()
{
return ToString(3);
}
/// <summary>
/// String representation of the top x entries
/// </summary>
/// <returns></returns>
public string ToString(int numberOfEntries)
{
var result = "";

View File

@ -4,17 +4,33 @@ using System.Linq;
namespace CryptoExchange.Net.RateLimiter
{
/// <summary>
/// Rate limiting object
/// </summary>
public class RateLimitObject
{
/// <summary>
/// Lock
/// </summary>
public object LockObject { get; }
private List<DateTime> Times { get; }
/// <summary>
/// ctor
/// </summary>
public RateLimitObject()
{
LockObject = new object();
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)
{
Times.RemoveAll(d => d < time - perTimePeriod);
@ -23,6 +39,10 @@ namespace CryptoExchange.Net.RateLimiter
return 0;
}
/// <summary>
/// Add an executed request time
/// </summary>
/// <param name="time"></param>
public void Add(DateTime time)
{
Times.Add(time);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,9 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net
{
/// <summary>
/// Base for socket client implementations
/// </summary>
public abstract class SocketClient: BaseClient, ISocketClient
{
#region fields
@ -25,6 +28,8 @@ namespace CryptoExchange.Net
/// List of socket connections currently connecting/connected
/// </summary>
protected internal ConcurrentDictionary<int, SocketConnection> sockets = new ConcurrentDictionary<int, SocketConnection>();
/// <summary>
/// </summary>
protected internal readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
/// <inheritdoc cref="SocketClientOptions.ReconnectInterval"/>
@ -42,14 +47,43 @@ namespace CryptoExchange.Net
/// <inheritdoc cref="SocketClientOptions.SocketSubscriptionsCombineTarget"/>
public int SocketCombineTarget { get; protected set; }
/// <summary>
/// Handler for byte data
/// </summary>
protected Func<byte[], string> dataInterpreterBytes;
/// <summary>
/// Handler for string data
/// </summary>
protected Func<string, string> dataInterpreterString;
/// <summary>
/// Generic handlers
/// </summary>
protected Dictionary<string, Action<SocketConnection, JToken>> genericHandlers = new Dictionary<string, Action<SocketConnection, JToken>>();
/// <summary>
/// Periodic task
/// </summary>
protected Task periodicTask;
/// <summary>
/// Periodic task event
/// </summary>
protected AutoResetEvent periodicEvent;
/// <summary>
/// Is disposing
/// </summary>
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
/// <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)
{
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);
}
/// <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)
{
return Query<T>(BaseAddress, request, authenticated);
@ -183,6 +224,7 @@ namespace CryptoExchange.Net
/// Query for data
/// </summary>
/// <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="authenticated">Whether the socket should be authenticated</param>
/// <returns></returns>
@ -293,7 +335,7 @@ namespace CryptoExchange.Net
/// <param name="s">The socket connection</param>
/// <param name="subscription"></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>
/// <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);

View File

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

View File

@ -11,25 +11,58 @@ using Newtonsoft.Json.Linq;
namespace CryptoExchange.Net.Sockets
{
/// <summary>
/// Socket connecting
/// </summary>
public class SocketConnection
{
/// <summary>
/// Connection lost event
/// </summary>
public event Action ConnectionLost;
/// <summary>
/// Connecting restored event
/// </summary>
public event Action<TimeSpan> ConnectionRestored;
/// <summary>
/// Connecting closed event
/// </summary>
public event Action Closed;
/// <summary>
/// The amount of handlers
/// </summary>
public int HandlerCount
{
get { lock (handlersLock)
return handlers.Count(h => h.UserSubscription); }
}
/// <summary>
/// If connection is authenticated
/// </summary>
public bool Authenticated { get; set; }
/// <summary>
/// If connection is made
/// </summary>
public bool Connected { get; private set; }
/// <summary>
/// The socket
/// </summary>
public IWebsocket Socket { get; set; }
/// <summary>
/// If should reconnect upon closing
/// </summary>
public bool ShouldReconnect { get; set; }
/// <summary>
/// Time of disconnecting
/// </summary>
public DateTime? DisconnectTime { get; set; }
/// <summary>
/// If activity is paused
/// </summary>
public bool PausedActivity { get; set; }
internal readonly List<SocketSubscription> handlers;
@ -41,6 +74,11 @@ namespace CryptoExchange.Net.Sockets
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)
{
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)
{
var handler = new SocketSubscription(null, request, userSubscription, dataHandler);
@ -80,6 +125,14 @@ namespace CryptoExchange.Net.Sockets
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)
{
var handler = new SocketSubscription(identifier, null, userSubscription, dataHandler);
@ -88,7 +141,7 @@ namespace CryptoExchange.Net.Sockets
return handler;
}
public void ProcessMessage(string data)
private void ProcessMessage(string data)
{
log.Write(LogVerbosity.Debug, $"Socket {Socket.Id} received data: " + data);
var tokenData = data.ToJToken(log);
@ -100,7 +153,9 @@ namespace CryptoExchange.Net.Sockets
if (pendingRequest.Check(tokenData))
{
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)
{
var pending = new PendingRequest(handler, timeout);
@ -230,7 +293,7 @@ namespace CryptoExchange.Net.Sockets
if (lostTriggered)
{
lostTriggered = false;
Task.Run(() => ConnectionRestored?.Invoke(DisconnectTime.HasValue ? DateTime.UtcNow - DisconnectTime.Value : TimeSpan.FromSeconds(0)));
InvokeConnectionRestored();
}
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)
{
@ -279,6 +347,10 @@ namespace CryptoExchange.Net.Sockets
return true;
}
/// <summary>
/// Close the connection
/// </summary>
/// <returns></returns>
public async Task Close()
{
Connected = false;
@ -290,6 +362,11 @@ namespace CryptoExchange.Net.Sockets
Socket.Dispose();
}
/// <summary>
/// Close the subscriptions
/// </summary>
/// <param name="subscription">Subscription to close</param>
/// <returns></returns>
public async Task Close(SocketSubscription subscription)
{
if (subscription.Confirmed)
@ -308,7 +385,7 @@ namespace CryptoExchange.Net.Sockets
}
}
public class PendingRequest
internal class PendingRequest
{
public Func<JToken, bool> Handler { get; }
public JToken Result { get; private set; }

View File

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

View File

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

View File

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