mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2025-05-06 11:32:59 +00:00
AsyncReply is awaitable
This commit is contained in:
parent
48e450ffc4
commit
2d9f61c0d9
@ -11,7 +11,7 @@
|
||||
<PackageProjectUrl>http://www.esiur.com</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/esiur/esiur-dotnet/</RepositoryUrl>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.2.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -185,7 +185,7 @@ namespace Esiur.Stores.MongoDB
|
||||
return rt;
|
||||
}
|
||||
|
||||
AsyncReply Parse(BsonValue value)
|
||||
IAsyncReply<object> Parse(BsonValue value)
|
||||
{
|
||||
if (value.BsonType == BsonType.Document)
|
||||
{
|
||||
@ -217,7 +217,7 @@ namespace Esiur.Stores.MongoDB
|
||||
return rt;
|
||||
}
|
||||
else
|
||||
return new AsyncReply(null);
|
||||
return new AsyncReply<object>(null);
|
||||
}
|
||||
else if (value.BsonType == BsonType.Array)
|
||||
{
|
||||
@ -233,12 +233,12 @@ namespace Esiur.Stores.MongoDB
|
||||
}
|
||||
else if (value.BsonType == BsonType.DateTime)
|
||||
{
|
||||
return new AsyncReply(value.ToUniversalTime());
|
||||
return new AsyncReply<object>(value.ToUniversalTime());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return new AsyncReply(value.RawValue);
|
||||
return new AsyncReply<object>(value.RawValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,7 +633,7 @@ namespace Esiur.Stores.MongoDB
|
||||
|
||||
var reply = new AsyncReply<KeyList<PropertyTemplate, PropertyValue[]>>();
|
||||
|
||||
AsyncBag<PropertyValue> bag = new AsyncBag<PropertyValue>();
|
||||
AsyncBag<PropertyValue[]> bag = new AsyncBag<PropertyValue[]>();
|
||||
|
||||
foreach (var p in properties)
|
||||
bag.Add(GetPropertyRecordByAge(resource, p.Name, fromAge, toAge));
|
||||
@ -642,7 +642,7 @@ namespace Esiur.Stores.MongoDB
|
||||
|
||||
bag.Then(x =>
|
||||
{
|
||||
var list = new KeyList<PropertyTemplate, PropertyValue>();
|
||||
var list = new KeyList<PropertyTemplate, PropertyValue[]>();
|
||||
|
||||
for (var i = 0; i < x.Length; i++)
|
||||
list.Add(properties[i], x[i]);
|
||||
@ -696,5 +696,10 @@ namespace Esiur.Stores.MongoDB
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Open(Structure settings)
|
||||
{
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ namespace Esiur.Data
|
||||
|
||||
var result = (StructureComparisonResult)data[offset++];
|
||||
|
||||
AsyncReply previous = null;
|
||||
IAsyncReply<Structure> previous = null;
|
||||
// string[] previousKeys = null;
|
||||
// DataType[] previousTypes = null;
|
||||
|
||||
@ -362,7 +362,7 @@ namespace Esiur.Data
|
||||
/// <param name="connection">DistributedConnection is required in case a structure in the array holds items at the other end.</param>
|
||||
/// <param name="dataType">DataType, in case the data is not prepended with DataType</param>
|
||||
/// <returns>Structure</returns>
|
||||
public static AsyncReply Parse(byte[] data, uint offset, DistributedConnection connection, DataType dataType = DataType.Unspecified)
|
||||
public static IAsyncReply<object> Parse(byte[] data, uint offset, DistributedConnection connection, DataType dataType = DataType.Unspecified)
|
||||
{
|
||||
uint size;
|
||||
return Parse(data, offset, out size, connection);
|
||||
@ -377,10 +377,9 @@ namespace Esiur.Data
|
||||
/// <param name="connection">DistributedConnection is required in case a structure in the array holds items at the other end.</param>
|
||||
/// <param name="dataType">DataType, in case the data is not prepended with DataType</param>
|
||||
/// <returns>Value</returns>
|
||||
public static AsyncReply Parse(byte[] data, uint offset, out uint size, DistributedConnection connection, DataType dataType = DataType.Unspecified)
|
||||
public static IAsyncReply<object> Parse(byte[] data, uint offset, out uint size, DistributedConnection connection, DataType dataType = DataType.Unspecified)
|
||||
{
|
||||
var reply = new AsyncReply();
|
||||
|
||||
|
||||
bool isArray;
|
||||
DataType t;
|
||||
|
||||
@ -480,40 +479,40 @@ namespace Esiur.Data
|
||||
return new AsyncReply<object>(null);
|
||||
|
||||
case DataType.Bool:
|
||||
return new AsyncReply<bool>(data.GetBoolean(offset));
|
||||
return new AsyncReply<object>(data.GetBoolean(offset));
|
||||
|
||||
case DataType.UInt8:
|
||||
return new AsyncReply<byte>(data[offset]);
|
||||
return new AsyncReply<object>(data[offset]);
|
||||
|
||||
case DataType.Int8:
|
||||
return new AsyncReply<sbyte>((sbyte)data[offset]);
|
||||
return new AsyncReply<object>((sbyte)data[offset]);
|
||||
|
||||
case DataType.Char:
|
||||
return new AsyncReply<char>(data.GetChar(offset));
|
||||
return new AsyncReply<object>(data.GetChar(offset));
|
||||
|
||||
case DataType.Int16:
|
||||
return new AsyncReply<short>(data.GetInt16(offset));
|
||||
return new AsyncReply<object>(data.GetInt16(offset));
|
||||
|
||||
case DataType.UInt16:
|
||||
return new AsyncReply<ushort>(data.GetUInt16(offset));
|
||||
return new AsyncReply<object>(data.GetUInt16(offset));
|
||||
|
||||
case DataType.Int32:
|
||||
return new AsyncReply<int>(data.GetInt32(offset));
|
||||
return new AsyncReply<object>(data.GetInt32(offset));
|
||||
|
||||
case DataType.UInt32:
|
||||
return new AsyncReply<uint>(data.GetUInt32(offset));
|
||||
return new AsyncReply<object>(data.GetUInt32(offset));
|
||||
|
||||
case DataType.Int64:
|
||||
return new AsyncReply<long>(data.GetInt64(offset));
|
||||
return new AsyncReply<object>(data.GetInt64(offset));
|
||||
|
||||
case DataType.UInt64:
|
||||
return new AsyncReply<ulong>(data.GetUInt64(offset));
|
||||
return new AsyncReply<object>(data.GetUInt64(offset));
|
||||
|
||||
case DataType.Float32:
|
||||
return new AsyncReply<float>(data.GetFloat32(offset));
|
||||
return new AsyncReply<object>(data.GetFloat32(offset));
|
||||
|
||||
case DataType.Float64:
|
||||
return new AsyncReply<double>(data.GetFloat64(offset));
|
||||
return new AsyncReply<object>(data.GetFloat64(offset));
|
||||
|
||||
case DataType.String:
|
||||
return new AsyncReply<string>(data.GetString(offset, contentLength));
|
||||
@ -525,7 +524,7 @@ namespace Esiur.Data
|
||||
return ParseDistributedResource(data, offset, connection);
|
||||
|
||||
case DataType.DateTime:
|
||||
return new AsyncReply<DateTime>(data.GetDateTime(offset));
|
||||
return new AsyncReply<object>(data.GetDateTime(offset));
|
||||
|
||||
case DataType.Structure:
|
||||
return ParseStructure(data, offset, contentLength, connection);
|
||||
@ -694,7 +693,7 @@ namespace Esiur.Data
|
||||
//
|
||||
var result = (ResourceComparisonResult)data[offset++];
|
||||
|
||||
AsyncReply previous = null;
|
||||
IAsyncReply<IResource> previous = null;
|
||||
|
||||
if (result == ResourceComparisonResult.Null)
|
||||
previous = new AsyncReply<IResource>(null);
|
||||
@ -716,7 +715,7 @@ namespace Esiur.Data
|
||||
{
|
||||
result = (ResourceComparisonResult)data[offset++];
|
||||
|
||||
AsyncReply current = null;
|
||||
IAsyncReply<IResource> current = null;
|
||||
|
||||
if (result == ResourceComparisonResult.Null)
|
||||
{
|
||||
|
40
Esiur/Engine/AsyncAwaiter.cs
Normal file
40
Esiur/Engine/AsyncAwaiter.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public class AsyncAwaiter<T> : INotifyCompletion
|
||||
{
|
||||
Action callback = null;
|
||||
T result;
|
||||
private bool completed;
|
||||
|
||||
public AsyncAwaiter(AsyncReply<T> reply)
|
||||
{
|
||||
reply.Then(x =>
|
||||
{
|
||||
completed = true;
|
||||
result = x;
|
||||
callback?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public T GetResult()
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsCompleted => completed;
|
||||
|
||||
//From INotifyCompletion
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
Console.WriteLine("Continue....");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -30,21 +30,23 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public class AsyncBag<T>:AsyncReply
|
||||
public class AsyncBag<T>: AsyncReply<T[]>
|
||||
{
|
||||
//Dictionary<AsyncReply, T> results = new Dictionary<AsyncReply, T>();
|
||||
|
||||
List<AsyncReply> replies = new List<AsyncReply>();
|
||||
List<IAsyncReply<T>> replies = new List<IAsyncReply<T>>();
|
||||
List<T> results = new List<T>();
|
||||
|
||||
int count = 0;
|
||||
bool sealedBag = false;
|
||||
|
||||
/*
|
||||
public AsyncBag<T> Then(Action<T[]> callback)
|
||||
{
|
||||
base.Then(new Action<object>(o => callback((T[])o)));
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
|
||||
public void Seal()
|
||||
{
|
||||
@ -72,7 +74,7 @@ namespace Esiur.Engine
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(AsyncReply reply)
|
||||
public void Add(IAsyncReply<T> reply)
|
||||
{
|
||||
if (!sealedBag)
|
||||
{
|
||||
@ -82,6 +84,12 @@ namespace Esiur.Engine
|
||||
//results.Add(reply, default(T));
|
||||
}
|
||||
|
||||
public void AddBag(AsyncBag<T> bag)
|
||||
{
|
||||
foreach (var r in bag.replies)
|
||||
Add(r);
|
||||
}
|
||||
|
||||
public AsyncBag()
|
||||
{
|
||||
|
||||
|
@ -63,23 +63,21 @@ namespace Esiur.Engine
|
||||
{
|
||||
|
||||
|
||||
AsyncReply.ErrorType type;
|
||||
ExceptionCode code;
|
||||
public readonly ErrorType Type;
|
||||
public readonly ExceptionCode Code;
|
||||
|
||||
public AsyncReply.ErrorType Type => type;
|
||||
public ExceptionCode Code => code;
|
||||
|
||||
public AsyncException(AsyncReply.ErrorType type, ushort code, string message)
|
||||
: base(type == AsyncReply.ErrorType.Management ? ((ExceptionCode)code).ToString() : message)
|
||||
public AsyncException(ErrorType type, ushort code, string message)
|
||||
: base(type == ErrorType.Management ? ((ExceptionCode)code).ToString() : message)
|
||||
{
|
||||
this.type = type;
|
||||
this.code = (ExceptionCode)code;
|
||||
this.Type = type;
|
||||
this.Code = (ExceptionCode)code;
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return code.ToString() + ": " + Message;
|
||||
return Code.ToString() + ": " + Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,18 +30,18 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public class AsyncQueue<T> : AsyncReply
|
||||
public class AsyncQueue<T> : AsyncReply<T>
|
||||
{
|
||||
List<AsyncReply<T>> list = new List<AsyncReply<T>>();
|
||||
//Action<T> callback;
|
||||
object queueLock = new object();
|
||||
|
||||
public AsyncQueue<T> Then(Action<T> callback)
|
||||
{
|
||||
base.Then(new Action<object>(o => callback((T)o)));
|
||||
//public AsyncQueue<T> Then(Action<T> callback)
|
||||
//{
|
||||
// base.Then(new Action<object>(o => callback((T)o)));
|
||||
|
||||
return this;
|
||||
}
|
||||
//return this;
|
||||
//}
|
||||
|
||||
public void Add(AsyncReply<T> reply)
|
||||
{
|
||||
|
@ -32,17 +32,9 @@ namespace Esiur.Engine
|
||||
{
|
||||
public class AsyncReply
|
||||
{
|
||||
public enum ErrorType
|
||||
{
|
||||
Management,
|
||||
Exception
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum ProgressType
|
||||
{
|
||||
Execution,
|
||||
Network,
|
||||
}
|
||||
|
||||
protected List<Action<object>> callbacks = new List<Action<object>>();
|
||||
protected object result;
|
||||
|
@ -29,13 +29,175 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Esiur.Resource;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public class AsyncReply<T>: AsyncReply
|
||||
public class AsyncReply<T>: IAsyncReply<T>
|
||||
{
|
||||
|
||||
protected List<Action<T>> callbacks = new List<Action<T>>();
|
||||
protected T result;
|
||||
|
||||
protected List<Action<AsyncException>> errorCallbacks = new List<Action<AsyncException>>();
|
||||
|
||||
public AsyncReply<T> Then(Action<T> callback)
|
||||
protected List<Action<ProgressType, int, int>> progressCallbacks = new List<Action<ProgressType, int, int>>();
|
||||
|
||||
protected List<Action<T>> chunkCallbacks = new List<Action<T>>();
|
||||
|
||||
//List<AsyncAwaiter> awaiters = new List<AsyncAwaiter>();
|
||||
|
||||
object callbacksLock = new object();
|
||||
|
||||
protected bool resultReady = false;
|
||||
AsyncException exception;
|
||||
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
|
||||
|
||||
public bool Ready
|
||||
{
|
||||
get { return resultReady; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public object Result
|
||||
{
|
||||
get { return result; }
|
||||
}
|
||||
|
||||
public IAsyncReply<T> Then(Action<T> callback)
|
||||
{
|
||||
callbacks.Add(callback);
|
||||
|
||||
if (resultReady)
|
||||
callback(result);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IAsyncReply<T> Error(Action<AsyncException> callback)
|
||||
{
|
||||
errorCallbacks.Add(callback);
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
callback(exception);
|
||||
tcs.SetException(exception);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IAsyncReply<T> Progress(Action<ProgressType, int, int> callback)
|
||||
{
|
||||
progressCallbacks.Add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public IAsyncReply<T> Chunk(Action<T> callback)
|
||||
{
|
||||
chunkCallbacks.Add(callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Trigger(object result)
|
||||
{
|
||||
|
||||
lock (callbacksLock)
|
||||
{
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
this.result = (T)result;
|
||||
resultReady = true;
|
||||
|
||||
foreach (var cb in callbacks)
|
||||
cb((T)result);
|
||||
|
||||
tcs.TrySetResult(result);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void TriggerError(AsyncException exception)
|
||||
{
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
this.exception = exception;
|
||||
|
||||
|
||||
lock (callbacksLock)
|
||||
{
|
||||
foreach (var cb in errorCallbacks)
|
||||
cb(exception);
|
||||
}
|
||||
|
||||
tcs.TrySetException(exception);
|
||||
}
|
||||
|
||||
public void TriggerProgress(ProgressType type, int value, int max)
|
||||
{
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
lock (callbacksLock)
|
||||
{
|
||||
foreach (var cb in progressCallbacks)
|
||||
cb(type, value, max);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void TriggerChunk(object value)
|
||||
{
|
||||
if (resultReady)
|
||||
return;
|
||||
|
||||
lock (callbacksLock)
|
||||
{
|
||||
foreach (var cb in chunkCallbacks)
|
||||
cb((T)value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return new AsyncAwaiter<T>(this);
|
||||
}
|
||||
|
||||
public Task Task
|
||||
{
|
||||
get
|
||||
{
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncReply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AsyncReply(T result)
|
||||
{
|
||||
resultReady = true;
|
||||
tcs.SetResult(result);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/*
|
||||
public AsyncReply<T> Then(Action<T> callback)
|
||||
{
|
||||
base.Then(new Action<object>(o => callback((T)o)));
|
||||
return this;
|
||||
@ -46,6 +208,15 @@ namespace Esiur.Engine
|
||||
Trigger((object)result);
|
||||
}
|
||||
|
||||
public Task<bool> MoveNext(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncReply()
|
||||
{
|
||||
|
||||
@ -67,13 +238,15 @@ namespace Esiur.Engine
|
||||
}
|
||||
}
|
||||
|
||||
public T Current => throw new NotImplementedException();
|
||||
|
||||
public AsyncReply(T result)
|
||||
: base(result)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
12
Esiur/Engine/ErrorType.cs
Normal file
12
Esiur/Engine/ErrorType.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public enum ErrorType
|
||||
{
|
||||
Management,
|
||||
Exception
|
||||
}
|
||||
}
|
21
Esiur/Engine/IAsyncReply.cs
Normal file
21
Esiur/Engine/IAsyncReply.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public interface IAsyncReply<out T>//IAsyncEnumerator<T>
|
||||
{
|
||||
IAsyncReply<T> Then(Action<T> callback);
|
||||
IAsyncReply<T> Error(Action<AsyncException> callback);
|
||||
IAsyncReply<T> Progress(Action<ProgressType, int, int> callback);
|
||||
IAsyncReply<T> Chunk(Action<T> callback);
|
||||
void Trigger(object result);
|
||||
void TriggerError(AsyncException exception);
|
||||
void TriggerProgress(ProgressType type, int value, int max);
|
||||
void TriggerChunk(object value);
|
||||
|
||||
|
||||
}
|
||||
}
|
12
Esiur/Engine/ProgressType.cs
Normal file
12
Esiur/Engine/ProgressType.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Engine
|
||||
{
|
||||
public enum ProgressType
|
||||
{
|
||||
Execution,
|
||||
Network,
|
||||
}
|
||||
}
|
@ -7,9 +7,11 @@
|
||||
<PackageLicenseUrl>https://github.com/esiur/esiur-dotnet/blob/master/LICENSE</PackageLicenseUrl>
|
||||
<PackageProjectUrl>http://www.esiur.com</PackageProjectUrl>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.2.3</Version>
|
||||
<RepositoryUrl>https://github.com/esiur/esiur-dotnet</RepositoryUrl>
|
||||
<Authors>Ahmed Kh. Zamil</Authors>
|
||||
<AssemblyVersion>1.2.3.0</AssemblyVersion>
|
||||
<Company>Esiur Foundation</Company>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@ -22,6 +24,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Engine\AsyncReply.cs" />
|
||||
<Compile Remove="Net\UDP\UDPServer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -30,12 +33,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Engine\AsyncReply.cs" />
|
||||
<None Include="Net\UDP\UDPServer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Diagnostics.StackTrace" Version="4.3.0" />
|
||||
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="3.2.0" />
|
||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.NetworkInformation" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.1" />
|
||||
|
@ -50,7 +50,7 @@ namespace Esiur.Net.DataLink
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,36 @@ namespace Esiur.Net.HTTP
|
||||
{
|
||||
public class IIPoWS: HTTPFilter
|
||||
{
|
||||
[ResourceProperty]
|
||||
public DistributedServer DistributedServer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override bool Execute(HTTPConnection sender)
|
||||
{
|
||||
|
||||
if (DistributedServer == null)
|
||||
return false;
|
||||
|
||||
var tcpSocket = sender.Unassign();
|
||||
|
||||
if (tcpSocket == null)
|
||||
return false;
|
||||
|
||||
var httpServer = sender.Parent;
|
||||
var wsSocket = new WSSocket(tcpSocket);
|
||||
httpServer.RemoveConnection(sender);
|
||||
|
||||
var iipConnection = new DistributedConnection();
|
||||
|
||||
DistributedServer.AddConnection(iipConnection);
|
||||
iipConnection.Assign(wsSocket);
|
||||
wsSocket.Begin();
|
||||
|
||||
return true;
|
||||
/*
|
||||
if (sender.Request.Filename.StartsWith("/iip/"))
|
||||
{
|
||||
// find the service
|
||||
@ -73,6 +101,7 @@ namespace Esiur.Net.HTTP
|
||||
}
|
||||
|
||||
return false;
|
||||
*/
|
||||
}
|
||||
|
||||
private void IipConnection_OnReady(DistributedConnection sender)
|
||||
|
@ -524,13 +524,13 @@ namespace Esiur.Net.IIP
|
||||
switch (packet.Report)
|
||||
{
|
||||
case IIPPacketReport.ManagementError:
|
||||
IIPReportError(packet.CallbackId, AsyncReply.ErrorType.Management, packet.ErrorCode, null);
|
||||
IIPReportError(packet.CallbackId, ErrorType.Management, packet.ErrorCode, null);
|
||||
break;
|
||||
case IIPPacketReport.ExecutionError:
|
||||
IIPReportError(packet.CallbackId, AsyncReply.ErrorType.Exception, packet.ErrorCode, packet.ErrorMessage);
|
||||
IIPReportError(packet.CallbackId, ErrorType.Exception, packet.ErrorCode, packet.ErrorMessage);
|
||||
break;
|
||||
case IIPPacketReport.ProgressReport:
|
||||
IIPReportProgress(packet.CallbackId, AsyncReply.ProgressType.Execution, packet.ProgressValue, packet.ProgressMax);
|
||||
IIPReportProgress(packet.CallbackId, ProgressType.Execution, packet.ProgressValue, packet.ProgressMax);
|
||||
break;
|
||||
case IIPPacketReport.ChunkStream:
|
||||
IIPReportChunk(packet.CallbackId, packet.Content);
|
||||
@ -735,5 +735,11 @@ namespace Esiur.Net.IIP
|
||||
// nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Open(Structure settings)
|
||||
{
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ namespace Esiur.Net.IIP
|
||||
|
||||
Dictionary<Guid, ResourceTemplate> templates = new Dictionary<Guid, ResourceTemplate>();
|
||||
|
||||
KeyList<uint, AsyncReply> requests = new KeyList<uint, AsyncReply>();
|
||||
KeyList<uint, IAsyncReply<object>> requests = new KeyList<uint, IAsyncReply<object>>();
|
||||
|
||||
uint callbackCounter = 0;
|
||||
|
||||
@ -62,7 +62,7 @@ namespace Esiur.Net.IIP
|
||||
/// <param name="action">Packet action.</param>
|
||||
/// <param name="args">Arguments to send.</param>
|
||||
/// <returns></returns>
|
||||
internal AsyncReply<object[]> SendRequest(IIPPacket.IIPPacketAction action, params object[] args)
|
||||
internal IAsyncReply<object[]> SendRequest(IIPPacket.IIPPacketAction action, params object[] args)
|
||||
{
|
||||
var reply = new AsyncReply<object[]>();
|
||||
callbackCounter++;
|
||||
@ -100,11 +100,11 @@ namespace Esiur.Net.IIP
|
||||
Send(bl.ToArray());
|
||||
}
|
||||
|
||||
internal AsyncReply SendInvokeByArrayArguments(uint instanceId, byte index, object[] parameters)
|
||||
internal AsyncReply<object> SendInvokeByArrayArguments(uint instanceId, byte index, object[] parameters)
|
||||
{
|
||||
var pb = Codec.ComposeVarArray(parameters, this, true);
|
||||
|
||||
var reply = new AsyncReply();
|
||||
var reply = new AsyncReply<object>();
|
||||
callbackCounter++;
|
||||
var bl = new BinaryList((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.InvokeFunctionArrayArguments),
|
||||
callbackCounter, instanceId, index, pb);
|
||||
@ -114,11 +114,11 @@ namespace Esiur.Net.IIP
|
||||
return reply;
|
||||
}
|
||||
|
||||
internal AsyncReply SendInvokeByNamedArguments(uint instanceId, byte index, Structure parameters)
|
||||
internal AsyncReply<object> SendInvokeByNamedArguments(uint instanceId, byte index, Structure parameters)
|
||||
{
|
||||
var pb = Codec.ComposeStructure(parameters, this, true, true, true);
|
||||
|
||||
var reply = new AsyncReply();
|
||||
var reply = new AsyncReply<object>();
|
||||
callbackCounter++;
|
||||
var bl = new BinaryList((byte)(0x40 | (byte)Packets.IIPPacket.IIPPacketAction.InvokeFunctionNamedArguments),
|
||||
callbackCounter, instanceId, index, pb);
|
||||
@ -129,12 +129,12 @@ namespace Esiur.Net.IIP
|
||||
}
|
||||
|
||||
|
||||
void SendError(AsyncReply.ErrorType type, uint callbackId, ushort errorCode, string errorMessage = "")
|
||||
void SendError(ErrorType type, uint callbackId, ushort errorCode, string errorMessage = "")
|
||||
{
|
||||
var msg = DC.ToBytes(errorMessage);
|
||||
if (type == AsyncReply.ErrorType.Management)
|
||||
if (type == ErrorType.Management)
|
||||
SendParams((byte)(0xC0 | (byte)IIPPacket.IIPPacketReport.ManagementError), callbackId, errorCode);
|
||||
else if (type == AsyncReply.ErrorType.Exception)
|
||||
else if (type == ErrorType.Exception)
|
||||
SendParams((byte)(0xC0 | (byte)IIPPacket.IIPPacketReport.ExecutionError), callbackId, errorCode, (ushort)msg.Length, msg);
|
||||
}
|
||||
|
||||
@ -165,13 +165,13 @@ namespace Esiur.Net.IIP
|
||||
});
|
||||
}
|
||||
|
||||
void IIPReportError(uint callbackId, AsyncReply.ErrorType errorType, ushort errorCode, string errorMessage)
|
||||
void IIPReportError(uint callbackId, ErrorType errorType, ushort errorCode, string errorMessage)
|
||||
{
|
||||
var req = requests.Take(callbackId);
|
||||
req?.TriggerError(new AsyncException(errorType, errorCode, errorMessage));
|
||||
}
|
||||
|
||||
void IIPReportProgress(uint callbackId, AsyncReply.ProgressType type, int value, int max)
|
||||
void IIPReportProgress(uint callbackId, ProgressType type, int value, int max)
|
||||
{
|
||||
var req = requests[callbackId];
|
||||
req?.TriggerProgress(type, value, max);
|
||||
@ -379,7 +379,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (res.Instance.Applicable(session, ActionType.Attach, null) == Ruling.Denied)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, 6);
|
||||
SendError(ErrorType.Management, callback, 6);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -415,7 +415,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
// reply failed
|
||||
//SendParams(0x80, r.Instance.Id, r.Instance.Age, r.Instance.Serialize(false, this));
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -459,7 +459,7 @@ namespace Esiur.Net.IIP
|
||||
else
|
||||
{
|
||||
// reply failed
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -480,7 +480,7 @@ namespace Esiur.Net.IIP
|
||||
else
|
||||
{
|
||||
// reply failed
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -492,20 +492,20 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (store == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.StoreNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.StoreNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(store is IStore))
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceIsNotStore);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceIsNotStore);
|
||||
return;
|
||||
}
|
||||
|
||||
// check security
|
||||
if (store.Instance.Applicable(session, ActionType.CreateResource, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.CreateDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.CreateDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -517,7 +517,7 @@ namespace Esiur.Net.IIP
|
||||
if (parent != null)
|
||||
if (parent.Instance.Applicable(session, ActionType.AddChild, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -537,7 +537,7 @@ namespace Esiur.Net.IIP
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ClassNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ClassNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -614,13 +614,13 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (r == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.Instance.Store.Instance.Applicable(session, ActionType.Delete, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.DeleteDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.DeleteDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -628,7 +628,7 @@ namespace Esiur.Net.IIP
|
||||
SendReply(IIPPacket.IIPPacketAction.DeleteResource, callback);
|
||||
//SendParams((byte)0x84, callback);
|
||||
else
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.DeleteFailed);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.DeleteFailed);
|
||||
});
|
||||
}
|
||||
|
||||
@ -638,14 +638,14 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (r == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!r.Instance.Store.Instance.Applicable(r, session, ActionType.InquireAttributes, null))
|
||||
if (r.Instance.Applicable(session, ActionType.InquireAttributes, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ViewAttributeDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ViewAttributeDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -660,7 +660,7 @@ namespace Esiur.Net.IIP
|
||||
SendReply(all ? IIPPacket.IIPPacketAction.GetAllAttributes : IIPPacket.IIPPacketAction.GetAttributes, callback,
|
||||
Codec.ComposeStructure(st, this, true, true, true));
|
||||
else
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.GetAttributesFailed);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.GetAttributesFailed);
|
||||
|
||||
});
|
||||
}
|
||||
@ -671,7 +671,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -679,19 +679,19 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (child == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent.Instance.Applicable(this.session, ActionType.AddChild, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
if (child.Instance.Applicable(this.session, ActionType.AddParent, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.AddParentDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddParentDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -710,7 +710,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -718,19 +718,19 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (child == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent.Instance.Applicable(this.session, ActionType.RemoveChild, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddChildDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
if (child.Instance.Applicable(this.session, ActionType.RemoveParent, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.AddParentDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.AddParentDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -749,13 +749,13 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (resource == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resource.Instance.Applicable(this.session, ActionType.Rename, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.RenameDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.RenameDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -771,7 +771,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (resource == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -789,7 +789,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (resource == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -807,13 +807,13 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (r == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.Instance.Store.Instance.Applicable(session, ActionType.UpdateAttributes, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -825,7 +825,7 @@ namespace Esiur.Net.IIP
|
||||
if (r.Instance.RemoveAttributes(attrs))
|
||||
SendReply(all ? IIPPacket.IIPPacketAction.ClearAllAttributes : IIPPacket.IIPPacketAction.ClearAttributes, callback);
|
||||
else
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeFailed);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeFailed);
|
||||
|
||||
});
|
||||
}
|
||||
@ -836,13 +836,13 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (r == null)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.Instance.Store.Instance.Applicable(session, ActionType.UpdateAttributes, null) != Ruling.Allowed)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeDenied);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -852,7 +852,7 @@ namespace Esiur.Net.IIP
|
||||
SendReply(clearAttributes ? IIPPacket.IIPPacketAction.ClearAllAttributes : IIPPacket.IIPPacketAction.ClearAttributes,
|
||||
callback);
|
||||
else
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeFailed);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.UpdateAttributeFailed);
|
||||
});
|
||||
|
||||
});
|
||||
@ -868,7 +868,7 @@ namespace Esiur.Net.IIP
|
||||
else
|
||||
{
|
||||
// reply failed
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -882,7 +882,7 @@ namespace Esiur.Net.IIP
|
||||
else
|
||||
{
|
||||
// reply failed
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -899,7 +899,7 @@ namespace Esiur.Net.IIP
|
||||
else
|
||||
{
|
||||
// reply failed
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.TemplateNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -916,7 +916,7 @@ namespace Esiur.Net.IIP
|
||||
var list = r.Where(x => x.Instance.Applicable(session, ActionType.Attach, null) != Ruling.Denied).ToArray();
|
||||
|
||||
if (list.Length == 0)
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
else
|
||||
SendReply(IIPPacket.IIPPacketAction.QueryLink, callback, Codec.ComposeResourceArray(list, this, true));
|
||||
//}
|
||||
@ -973,7 +973,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback,
|
||||
SendError(ErrorType.Management, callback,
|
||||
(ushort)ExceptionCode.InvokeDenied);
|
||||
return;
|
||||
}
|
||||
@ -1009,7 +1009,7 @@ namespace Esiur.Net.IIP
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Exception, callback, 0, ex.ToString());
|
||||
SendError(ErrorType.Exception, callback, 0, ex.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1038,14 +1038,15 @@ namespace Esiur.Net.IIP
|
||||
//await t;
|
||||
//SendParams((byte)0x90, callback, Codec.Compose(res, this));
|
||||
}
|
||||
else if (rt is AsyncReply) //(rt.GetType().IsGenericType && (rt.GetType().GetGenericTypeDefinition() == typeof(AsyncReply<>)))
|
||||
else if (rt.GetType().GetTypeInfo().IsGenericType
|
||||
&& rt.GetType().GetGenericTypeDefinition() == typeof(IAsyncReply<>))
|
||||
{
|
||||
(rt as AsyncReply).Then(res =>
|
||||
(rt as IAsyncReply<object>).Then(res =>
|
||||
{
|
||||
SendReply(IIPPacket.IIPPacketAction.InvokeFunctionArrayArguments, callback, Codec.Compose(res, this));
|
||||
}).Error(ex =>
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Exception, callback, (ushort)ex.Code, ex.Message);
|
||||
SendError(ErrorType.Exception, callback, (ushort)ex.Code, ex.Message);
|
||||
}).Progress((pt, pv, pm) =>
|
||||
{
|
||||
SendProgress(callback, pv, pm);
|
||||
@ -1119,7 +1120,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
if (r.Instance.Applicable(session, ActionType.Execute, ft) == Ruling.Denied)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback,
|
||||
SendError(ErrorType.Management, callback,
|
||||
(ushort)ExceptionCode.InvokeDenied);
|
||||
return;
|
||||
}
|
||||
@ -1150,7 +1151,7 @@ namespace Esiur.Net.IIP
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Exception, callback, 0, ex.ToString());
|
||||
SendError(ErrorType.Exception, callback, 0, ex.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1177,14 +1178,16 @@ namespace Esiur.Net.IIP
|
||||
});
|
||||
|
||||
}
|
||||
else if (rt is AsyncReply)
|
||||
{
|
||||
(rt as AsyncReply).Then(res =>
|
||||
// else if (rt is AsyncReply)
|
||||
else if (rt.GetType().GetTypeInfo().IsGenericType
|
||||
&& rt.GetType().GetGenericTypeDefinition() == typeof(IAsyncReply<>))
|
||||
{
|
||||
(rt as IAsyncReply<object>).Then(res =>
|
||||
{
|
||||
SendReply(IIPPacket.IIPPacketAction.InvokeFunctionNamedArguments, callback, Codec.Compose(res, this));
|
||||
}).Error(ex =>
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Exception, callback, (ushort)ex.Code, ex.Message);
|
||||
SendError(ErrorType.Exception, callback, (ushort)ex.Code, ex.Message);
|
||||
}).Progress((pt, pv, pm) =>
|
||||
{
|
||||
SendProgress(callback, pv, pm);
|
||||
@ -1376,13 +1379,13 @@ namespace Esiur.Net.IIP
|
||||
|
||||
if (r.Instance.Applicable(session, ActionType.SetProperty, pt, this) == Ruling.Denied)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Exception, callback, (ushort)ExceptionCode.SetPropertyDenied);
|
||||
SendError(ErrorType.Exception, callback, (ushort)ExceptionCode.SetPropertyDenied);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pi.CanWrite)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ReadOnlyProperty);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ReadOnlyProperty);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1405,14 +1408,14 @@ namespace Esiur.Net.IIP
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
SendError(AsyncReply.ErrorType.Exception, callback, 0, ex.Message);
|
||||
SendError(ErrorType.Exception, callback, 0, ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// pt found, pi not found, this should never happen
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1421,13 +1424,13 @@ namespace Esiur.Net.IIP
|
||||
else
|
||||
{
|
||||
// property not found
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.PropertyNotFound);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// resource not found
|
||||
SendError(AsyncReply.ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
SendError(ErrorType.Management, callback, (ushort)ExceptionCode.ResourceNotFound);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1718,7 +1721,7 @@ namespace Esiur.Net.IIP
|
||||
|
||||
SendRequest(IIPPacket.IIPPacketAction.ResourceChildren, resource.Instance.Id).Then(ar =>
|
||||
{
|
||||
var d = (byte[])ar;
|
||||
var d = (byte[])ar[0];
|
||||
Codec.ParseResourceArray(d, 0, (uint)d.Length, this).Then(resources =>
|
||||
{
|
||||
rt.Trigger(resources);
|
||||
@ -1734,7 +1737,7 @@ namespace Esiur.Net.IIP
|
||||
|
||||
SendRequest(IIPPacket.IIPPacketAction.ResourceParents, resource.Instance.Id).Then(ar =>
|
||||
{
|
||||
var d = (byte[])ar;
|
||||
var d = (byte[])ar[0];
|
||||
Codec.ParseResourceArray(d, 0, (uint)d.Length, this).Then(resources =>
|
||||
{
|
||||
rt.Trigger(resources);
|
||||
|
@ -234,7 +234,7 @@ namespace Esiur.Net.IIP
|
||||
Instance.EmitResourceEvent(null, null, et.Name, args);
|
||||
}
|
||||
|
||||
public AsyncReply _InvokeByNamedArguments(byte index, Structure namedArgs)
|
||||
public AsyncReply<object> _InvokeByNamedArguments(byte index, Structure namedArgs)
|
||||
{
|
||||
if (destroyed)
|
||||
throw new Exception("Trying to access destroyed object");
|
||||
@ -246,7 +246,7 @@ namespace Esiur.Net.IIP
|
||||
return connection.SendInvokeByNamedArguments(instanceId, index, namedArgs);
|
||||
}
|
||||
|
||||
public AsyncReply _InvokeByArrayArguments(byte index, object[] args)
|
||||
public AsyncReply<object> _InvokeByArrayArguments(byte index, object[] args)
|
||||
{
|
||||
if (destroyed)
|
||||
throw new Exception("Trying to access destroyed object");
|
||||
@ -263,7 +263,7 @@ namespace Esiur.Net.IIP
|
||||
{
|
||||
var ft = Instance.Template.GetFunctionTemplate(binder.Name);
|
||||
|
||||
var reply = new AsyncReply();
|
||||
var reply = new AsyncReply<object>();
|
||||
|
||||
if (isAttached && ft!=null)
|
||||
{
|
||||
@ -358,12 +358,12 @@ namespace Esiur.Net.IIP
|
||||
/// <param name="index">Zero-based property index.</param>
|
||||
/// <param name="value">Value</param>
|
||||
/// <returns>Indicator when the property is set.</returns>
|
||||
internal AsyncReply _Set(byte index, object value)
|
||||
internal AsyncReply<object> _Set(byte index, object value)
|
||||
{
|
||||
if (index >= properties.Length)
|
||||
return null;
|
||||
|
||||
var reply = new AsyncReply();
|
||||
var reply = new AsyncReply<object>();
|
||||
|
||||
var parameters = Codec.Compose(value, connection);
|
||||
connection.SendRequest(Packets.IIPPacket.IIPPacketAction.SetProperty, instanceId, index, parameters).Then((res) =>
|
||||
|
@ -35,6 +35,7 @@ namespace Esiur.Resource
|
||||
{
|
||||
public interface IStore:IResource
|
||||
{
|
||||
AsyncReply<bool> Open(Structure settings);
|
||||
AsyncReply<IResource> Get(string path);
|
||||
AsyncReply<IResource> Retrieve(uint iid);
|
||||
bool Put(IResource resource);
|
||||
|
@ -30,7 +30,7 @@ namespace Esiur.Resource.Template
|
||||
if (Expansion != null)
|
||||
{
|
||||
var exp = DC.ToBytes(Expansion);
|
||||
return BinaryList.ToBytes((byte)(0x10 | (IsVoid ? 0x8 : 0x0)), exp.Length, exp, (byte)name.Length, name);
|
||||
return BinaryList.ToBytes((byte)(0x10 | (IsVoid ? 0x8 : 0x0)), (byte)name.Length, name, exp.Length, exp);
|
||||
}
|
||||
else
|
||||
return BinaryList.ToBytes((byte)(IsVoid ? 0x8 : 0x0), (byte)name.Length, name);
|
||||
|
@ -66,17 +66,17 @@ namespace Esiur.Resource.Template
|
||||
{
|
||||
var rexp = DC.ToBytes(ReadExpansion);
|
||||
var wexp = DC.ToBytes(WriteExpansion);
|
||||
return BinaryList.ToBytes((byte)(0x38 | pv), wexp.Length, wexp, rexp.Length, rexp, (byte)name.Length, name);
|
||||
return BinaryList.ToBytes((byte)(0x38 | pv), (byte)name.Length, name, wexp.Length, wexp, rexp.Length, rexp);
|
||||
}
|
||||
else if (WriteExpansion != null)
|
||||
{
|
||||
var wexp = DC.ToBytes(WriteExpansion);
|
||||
return BinaryList.ToBytes((byte)(0x30 | pv), wexp.Length, wexp, (byte)name.Length, name);
|
||||
return BinaryList.ToBytes((byte)(0x30 | pv), (byte)name.Length, name, wexp.Length, wexp);
|
||||
}
|
||||
else if (ReadExpansion != null)
|
||||
{
|
||||
var rexp = DC.ToBytes(ReadExpansion);
|
||||
return BinaryList.ToBytes((byte)(0x28 | pv), rexp.Length, rexp, (byte)name.Length, name);
|
||||
return BinaryList.ToBytes((byte)(0x28 | pv), (byte)name.Length, name, rexp.Length, rexp);
|
||||
}
|
||||
else
|
||||
return BinaryList.ToBytes((byte)(0x20 | pv), (byte)name.Length, name);
|
||||
|
@ -228,7 +228,7 @@ namespace Esiur.Resource.Template
|
||||
|
||||
od.classId = data.GetGuid(offset);
|
||||
offset += 16;
|
||||
od.className = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]);
|
||||
od.className = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
od.version = data.GetInt32(offset);
|
||||
@ -250,7 +250,7 @@ namespace Esiur.Resource.Template
|
||||
string expansion = null;
|
||||
var hasExpansion = ((data[offset] & 0x10) == 0x10);
|
||||
var isVoid = ((data[offset++] & 0x08) == 0x08);
|
||||
var name = data.GetString(offset + 1, data[offset]);// Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]);
|
||||
var name = data.GetString(offset + 1, data[offset]);
|
||||
offset += (uint)data[offset] + 1;
|
||||
|
||||
if (hasExpansion) // expansion ?
|
||||
|
@ -53,6 +53,7 @@ namespace Esiur.Resource
|
||||
public static event StoreConnectedEvent StoreConnected;
|
||||
public static event StoreDisconnectedEvent StoreDisconnected;
|
||||
|
||||
static KeyList<string, IStore> protocols = new KeyList<string, IStore>();
|
||||
|
||||
/// <summary>
|
||||
/// Get a store by its name.
|
||||
@ -234,7 +235,7 @@ namespace Esiur.Resource
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns>Resource instance.</returns>
|
||||
public static AsyncReply<IResource> Get(string path)
|
||||
public static AsyncReply<IResource> Get(string path, Structure settings = null, IResource parent = null, IPermissionsManager manager = null)
|
||||
{
|
||||
|
||||
var p = path.Split('/');
|
||||
@ -265,6 +266,41 @@ namespace Esiur.Resource
|
||||
return new AsyncReply<IResource>(res);
|
||||
}
|
||||
|
||||
// Should we create a new store ?
|
||||
if (path.Contains("://"))
|
||||
{
|
||||
var url = path.Split(new string[] { "://" }, 2, StringSplitOptions.None);
|
||||
var hostname = url[1].Split(new char[] { '/' }, 2)[0];
|
||||
var pathname = string.Join("/", url[1].Split(new char[] { '/' }).Skip(1));
|
||||
|
||||
|
||||
var rt = new AsyncReply<IResource>();
|
||||
|
||||
if (protocols.ContainsKey(url[0]))
|
||||
{
|
||||
var handler = protocols[url[0]];
|
||||
|
||||
var store = Activator.CreateInstance(handler.GetType()) as IStore;
|
||||
Put(store, url[0] + "://" + hostname, null, parent, null, 0, manager);
|
||||
|
||||
store.Open(settings).Then(x => {
|
||||
if (pathname.Length > 0 && pathname != "")
|
||||
store.Get(pathname).Then(r => {
|
||||
rt.Trigger(r);
|
||||
}).Error(e => rt.TriggerError(e));
|
||||
else
|
||||
rt.Trigger(store);
|
||||
|
||||
}).Error(e => {
|
||||
rt.TriggerError(e);
|
||||
Warehouse.Remove(store);
|
||||
});
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
return new AsyncReply<IResource>(null);
|
||||
}
|
||||
|
||||
|
@ -75,5 +75,10 @@ namespace Esiur.Stores
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Open(Structure settings)
|
||||
{
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,10 @@ namespace Test
|
||||
return Level;
|
||||
}
|
||||
|
||||
[ResourceFunction]
|
||||
public double Divide(float nominator, float denominator, DistributedConnection sender)
|
||||
[ResourceFunction("Divide takes two arguments nominator and denominator")]
|
||||
public double Divide(float n, float d, DistributedConnection sender)
|
||||
{
|
||||
return nominator / denominator;
|
||||
return n / d;
|
||||
}
|
||||
|
||||
[ResourceFunction]
|
||||
@ -50,7 +50,7 @@ namespace Test
|
||||
return Level;
|
||||
}
|
||||
|
||||
[ResourceFunction]
|
||||
[ResourceFunction("use it with next()")]
|
||||
public IEnumerable<string> Enum(int count)
|
||||
{
|
||||
var msg = new string[] { "Have you throught what if a function has multiple returns ?", "So you can return chunks of IO operation that not yet finished.", "Also, what about the progress ?", "This is an example of both.", "Use it anyway you like" };
|
||||
@ -62,7 +62,7 @@ namespace Test
|
||||
}
|
||||
}
|
||||
|
||||
[ResourceFunction]
|
||||
[ResourceFunction("Stream returns progress")]
|
||||
public AsyncReply<string> Stream(int count)
|
||||
{
|
||||
var reply = new AsyncReply<string>();
|
||||
@ -73,7 +73,7 @@ namespace Test
|
||||
timer = new Timer((x) =>
|
||||
{
|
||||
|
||||
reply.TriggerProgress(AsyncReply.ProgressType.Execution, count, 22);
|
||||
reply.TriggerProgress(ProgressType.Execution, count, 22);
|
||||
|
||||
if (count % 2 == 0 && msgCounter < msg.Length)
|
||||
reply.TriggerChunk(msg[msgCounter++]);
|
||||
|
133
Test/Program.cs
133
Test/Program.cs
@ -43,15 +43,10 @@ namespace Test
|
||||
static DistributedResource remoteObject;
|
||||
|
||||
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
MainAsync().Wait();
|
||||
//Thread.Sleep(-1);
|
||||
}
|
||||
|
||||
static async Task MainAsync()
|
||||
{
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
//AsyncContext.Run(() => ());
|
||||
|
||||
// Create stores to keep objects.
|
||||
@ -59,65 +54,71 @@ namespace Test
|
||||
var remote = Warehouse.New<MemoryStore>("remote");
|
||||
var mongo = Warehouse.New<MongoDBStore>("db");
|
||||
|
||||
/*
|
||||
var system = await Warehouse.Get("mem://system").Task;
|
||||
var remote = await Warehouse.Get("mem://remote").Task;
|
||||
var mongo = await Warehouse.Get("mongo://db").Task;
|
||||
var iip = await Warehouse.Get("iip://:5000").Task;
|
||||
var iws = await Warehouse.Get("iipows://:5001", new Structure() { ["iip"] = iip }).Task;
|
||||
*/
|
||||
|
||||
var ok = await Warehouse.Open();
|
||||
|
||||
|
||||
// Open the warehouse
|
||||
var ok = await Warehouse.Open().Task;
|
||||
|
||||
|
||||
// Create new object if the store is empty
|
||||
if (mongo.Count == 0)
|
||||
myObject = Warehouse.New<MyObject>("my", mongo, null,
|
||||
new UserPermissionsManager(new Structure()
|
||||
{
|
||||
["demo@localhost"] = new Structure()
|
||||
{
|
||||
["Subtract"] = new Structure { ["Execute"] = "yes" },
|
||||
["Stream"] = new Structure { ["Execute"] = "yes" },
|
||||
["_attach"] = "yes",
|
||||
["_get_attributes"] = "yes",
|
||||
["_set_attributes"] = "yes",
|
||||
}
|
||||
}));
|
||||
else
|
||||
Warehouse.Get("db/my").Then((o) => { myObject = (MyObject)o; });
|
||||
|
||||
// Create new distributed server object
|
||||
var iip = Warehouse.New<DistributedServer>("iip", system);
|
||||
// Set membership which handles authentication.
|
||||
iip.Membership = Warehouse.New<MyMembership>("ms", system);
|
||||
// Start the server on port 5000.
|
||||
iip.Start(new TCPSocket(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5000)), 600000, 60000);
|
||||
|
||||
|
||||
// Create http server to handle IIP over Websockets
|
||||
var http = Warehouse.New<HTTPServer>("http", system);
|
||||
http.Start(new TCPSocket(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5001)), 600000, 60000);
|
||||
|
||||
// Create IIP over Websocket HTTP module and give it to HTTP server.
|
||||
var wsOverHttp = Warehouse.New<IIPoWS>("IIPoWS", system, http);
|
||||
|
||||
|
||||
|
||||
Warehouse.StoreConnected += (store, name) =>
|
||||
{
|
||||
if (store.Instance.Parents.Contains(iip))
|
||||
// Create new object if the store is empty
|
||||
if (mongo.Count == 0)
|
||||
myObject = Warehouse.New<MyObject>("my", mongo, null,
|
||||
new UserPermissionsManager(new Structure()
|
||||
{
|
||||
store.Get("local/js").Then((r) =>
|
||||
["demo@localhost"] = new Structure()
|
||||
{
|
||||
if (r != null)
|
||||
{
|
||||
dynamic d = r;
|
||||
d.send("Welcome");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
["Subtract"] = new Structure { ["Execute"] = "yes" },
|
||||
["Stream"] = new Structure { ["Execute"] = "yes" },
|
||||
["_attach"] = "yes",
|
||||
["_get_attributes"] = "yes",
|
||||
["_set_attributes"] = "yes",
|
||||
}
|
||||
}));
|
||||
else
|
||||
myObject =(MyObject) (await Warehouse.Get("db/my"));//.Then((o) => { myObject = (MyObject)o; });
|
||||
|
||||
// Start testing
|
||||
// TestClient();
|
||||
|
||||
// Create new distributed server object
|
||||
var iip = Warehouse.New<DistributedServer>("iip", system);
|
||||
// Set membership which handles authentication.
|
||||
iip.Membership = Warehouse.New<MyMembership>("ms", system);
|
||||
// Start the server on port 5000.
|
||||
iip.Start(new TCPSocket(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5000)), 600000, 60000);
|
||||
|
||||
|
||||
// Create http server to handle IIP over Websockets
|
||||
var http = Warehouse.New<HTTPServer>("http", system);
|
||||
http.Start(new TCPSocket(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5001)), 600000, 60000);
|
||||
|
||||
// Create IIP over Websocket HTTP module and give it to HTTP server.
|
||||
var wsOverHttp = Warehouse.New<IIPoWS>("IIPoWS", system, http);
|
||||
|
||||
wsOverHttp.DistributedServer = iip;
|
||||
|
||||
Warehouse.StoreConnected += (store, name) =>
|
||||
{
|
||||
if (store.Instance.Parents.Contains(iip))
|
||||
{
|
||||
store.Get("local/js").Then((r) =>
|
||||
{
|
||||
if (r != null)
|
||||
{
|
||||
dynamic d = r;
|
||||
d.send("Welcome");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Start testing
|
||||
// TestClient();
|
||||
|
||||
var running = true;
|
||||
|
||||
@ -137,6 +138,8 @@ namespace Test
|
||||
else
|
||||
Console.WriteLine(myObject.Name + " " + myObject.Level);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static void TestClient()
|
||||
@ -148,12 +151,12 @@ namespace Test
|
||||
// Put the client in our memory store
|
||||
var remote = Warehouse.GetStore("remote");
|
||||
Warehouse.Put(client, "Endpoint", remote);
|
||||
|
||||
|
||||
|
||||
|
||||
client.OnReady += async (c) =>
|
||||
{
|
||||
// Get remote object from the server.
|
||||
remoteObject = await client.Get("db/my").Task as DistributedResource;
|
||||
//remoteObject = await client.Get("db/my").Task as DistributedResource;
|
||||
|
||||
dynamic x = remoteObject;
|
||||
|
||||
@ -169,13 +172,13 @@ namespace Test
|
||||
Console.WriteLine("LevelUp " + parameters[0] + " " + parameters[1]);
|
||||
});
|
||||
|
||||
(x.Stream(10) as AsyncReply).Then(r =>
|
||||
(x.Stream(10) as AsyncReply<object>).Then(r =>
|
||||
{
|
||||
Console.WriteLine("Stream ended: " + r);
|
||||
}).Chunk(r=>
|
||||
}).Chunk(r =>
|
||||
{
|
||||
Console.WriteLine("Chunk..." + r);
|
||||
}).Progress((t, v, m)=> Console.WriteLine("Processing {0}/{1}", v, m));
|
||||
}).Progress((t, v, m) => Console.WriteLine("Processing {0}/{1}", v, m));
|
||||
|
||||
var rt = await x.Subtract(10).Task;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user