mirror of https://github.com/icedream/npsharp.git
File name capitalization.
parent
2e9fda772a
commit
469eb15f2e
|
@ -0,0 +1,221 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using log4net;
|
||||
using NPSharp.RPC;
|
||||
using NPSharp.RPC.Packets;
|
||||
|
||||
namespace NPSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a high-level network platform client.
|
||||
/// </summary>
|
||||
public class NPClient
|
||||
{
|
||||
private readonly RPCClientStream _rpc;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private CancellationToken _cancellationToken;
|
||||
private ILog _log;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the NP client with a specified host and port.
|
||||
/// </summary>
|
||||
/// <param name="host">The host to connect to.</param>
|
||||
/// <param name="port">The port to use. Default: 3025.</param>
|
||||
public NPClient(string host, ushort port = 3025)
|
||||
{
|
||||
_rpc = new RPCClientStream(host, port);
|
||||
_log = LogManager.GetLogger ("NPClient");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The assigned NP user ID. Will be set on successful authentication.
|
||||
/// </summary>
|
||||
public ulong LoginId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The assigned session token for this client. Will be set on successful authentication.
|
||||
/// </summary>
|
||||
public string SessionToken { get; private set; }
|
||||
|
||||
// TODO: Handle connection failures via exception
|
||||
/// <summary>
|
||||
/// Connects the client to the NP server.
|
||||
/// </summary>
|
||||
/// <returns>True if the connection succeeded, otherwise false.</returns>
|
||||
public bool Connect()
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationToken = _cancellationTokenSource.Token;
|
||||
|
||||
if (!_rpc.Open())
|
||||
return false;
|
||||
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
_log.Debug("Now receiving RPC messages");
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var message = _rpc.Read();
|
||||
if (message == null)
|
||||
continue;
|
||||
|
||||
// TODO: log4net
|
||||
Console.WriteLine("Received packet ID {1} (type {0})", message.GetType().Name, message.MessageId);
|
||||
}
|
||||
}
|
||||
catch (ProtocolViolationException error)
|
||||
{
|
||||
_log.ErrorFormat("Protocol violation: {0}. Disconnect imminent.", error.Message);
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
_log.Debug("Now not receiving RPC messages anymore");
|
||||
}, _cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the client from the NP server.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
_cancellationTokenSource.Cancel(true); // TODO: Find a cleaner way to cancel _processingTask (focus: _rpc.Read)
|
||||
_rpc.Close();
|
||||
|
||||
LoginId = 0;
|
||||
}
|
||||
|
||||
// TODO: Try to use an exception for failed action instead
|
||||
/// <summary>
|
||||
/// Authenticates this connection via a token. This token has to be requested via an external interface like remauth.php.
|
||||
/// </summary>
|
||||
/// <param name="token">The token to use for authentication</param>
|
||||
/// <returns>True if the login succeeded, otherwise false.</returns>
|
||||
public async Task<bool> AuthenticateWithToken(string token)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
_rpc.AttachCallback(packet =>
|
||||
{
|
||||
var result = (AuthenticateResultMessage) packet;
|
||||
if (result.Result != 0)
|
||||
tcs.SetResult(false);
|
||||
LoginId = result.NPID;
|
||||
SessionToken = result.SessionToken;
|
||||
tcs.SetResult(true);
|
||||
});
|
||||
_rpc.Send(new AuthenticateWithTokenMessage {Token = token});
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
// TODO: Try to use an exception for failed action instead
|
||||
/// <summary>
|
||||
/// Uploads a user file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name to save the contents to on the server</param>
|
||||
/// <param name="contents">The raw byte contents</param>
|
||||
/// <returns>True if the upload succeeded, otherwise false.</returns>
|
||||
public async Task<bool> UploadUserFile(string filename, byte[] contents)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
_rpc.AttachCallback(packet =>
|
||||
{
|
||||
var result = (StorageWriteUserFileResultMessage) packet;
|
||||
if (result.Result != 0)
|
||||
tcs.SetResult(false);
|
||||
tcs.SetResult(true);
|
||||
});
|
||||
_rpc.Send(new StorageWriteUserFileMessage {FileData = contents, FileName = filename, NPID = LoginId});
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a user file and returns its contents.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file to download</param>
|
||||
/// <returns>File contents as byte array</returns>
|
||||
public async Task<byte[]> GetUserFile(string filename)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<byte[]>();
|
||||
|
||||
_rpc.AttachCallback(packet =>
|
||||
{
|
||||
var result = (StorageUserFileMessage) packet;
|
||||
if (result.Result != 0)
|
||||
{
|
||||
tcs.SetException(new NpFileException());
|
||||
return;
|
||||
}
|
||||
tcs.SetResult(result.FileData);
|
||||
});
|
||||
_rpc.Send(new StorageGetUserFileMessage {FileName = filename, NPID = LoginId});
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a user file onto the harddisk.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file to download</param>
|
||||
/// <param name="targetpath">Path where to save the file</param>
|
||||
public async void DownloadUserFileTo(string filename, string targetpath)
|
||||
{
|
||||
var contents = await GetUserFile(filename);
|
||||
|
||||
File.WriteAllBytes(targetpath, contents);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a publisher file and returns its contents.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file to download</param>
|
||||
/// <returns>File contents as byte array</returns>
|
||||
public async Task<byte[]> GetPublisherFile(string filename)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<byte[]>();
|
||||
|
||||
_rpc.AttachCallback(packet =>
|
||||
{
|
||||
var result = (StoragePublisherFileMessage) packet;
|
||||
if (result.Result != 0)
|
||||
{
|
||||
tcs.SetException(new NpFileException());
|
||||
return;
|
||||
}
|
||||
tcs.SetResult(result.FileData);
|
||||
});
|
||||
_rpc.Send(new StorageGetPublisherFileMessage {FileName = filename});
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a publisher file onto the harddisk.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file to download</param>
|
||||
/// <param name="targetpath">Path where to save the file</param>
|
||||
public async void DownloadPublisherFileTo(string filename, string targetpath)
|
||||
{
|
||||
var contents = await GetPublisherFile(filename);
|
||||
|
||||
File.WriteAllBytes(targetpath, contents);
|
||||
}
|
||||
|
||||
public void SendRandomString(string data)
|
||||
{
|
||||
_rpc.Send(new StorageSendRandomStringMessage() { RandomString=data });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1006)]
|
||||
[ProtoContract]
|
||||
class AuthenticateExternalStatusMessage : RPCServerMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int Status { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1010)]
|
||||
[ProtoContract]
|
||||
class AuthenticateResultMessage : RPCServerMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int Result { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public ulong NPID { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string SessionToken { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1003)]
|
||||
[ProtoContract]
|
||||
class AuthenticateWithTokenMessage : RPCClientMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(2001)]
|
||||
[ProtoContract]
|
||||
class CloseAppMessage : RPCServerMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string Reason { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1000)]
|
||||
[ProtoContract]
|
||||
class HelloMessage : RPCServerMessage
|
||||
{
|
||||
// I seriously have no idea where in the code this is used but whatever
|
||||
[ProtoMember(1)]
|
||||
public int Number1 { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public int Number2 { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public string String2 { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(2002)]
|
||||
[ProtoContract]
|
||||
class MessagingSendDataMessage : RPCClientMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public ulong NPID { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
class PacketAttribute : Attribute
|
||||
{
|
||||
public PacketAttribute(uint type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public uint Type { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
public class RPCClientMessage : RPCMessage
|
||||
{
|
||||
public byte[] Serialize(uint id)
|
||||
{
|
||||
byte[] content;
|
||||
using (var bufferStream = new MemoryStream())
|
||||
{
|
||||
ProtoBuf.Serializer.Serialize(bufferStream, this);
|
||||
bufferStream.Seek(0, SeekOrigin.Begin);
|
||||
content = bufferStream.ToArray();
|
||||
}
|
||||
|
||||
var buffer = new List<byte>();
|
||||
buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(Signature)));
|
||||
buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(content.Length)));
|
||||
buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(GetTypeId())));
|
||||
buffer.AddRange(BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder(id)));
|
||||
buffer.AddRange(content);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.Linq;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
public class RPCMessage
|
||||
{
|
||||
internal const uint Signature = 0xDEADC0DE; // I wonder if aiw3 changed this since kernal noted it in his source code.
|
||||
|
||||
public uint GetTypeId()
|
||||
{
|
||||
var packet = (PacketAttribute) GetType().GetCustomAttributes(typeof (PacketAttribute), false).Single();
|
||||
return packet.Type;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
public class RPCServerMessage : RPCMessage
|
||||
{
|
||||
public uint MessageId { get; private set; }
|
||||
|
||||
public static RPCServerMessage Deserialize(NetworkStream ns)
|
||||
{
|
||||
var header = new byte[16];
|
||||
var l = ns.Read(header, 0, header.Length);
|
||||
if (l == 0)
|
||||
return null;
|
||||
if (l < 16)
|
||||
throw new ProtocolViolationException("Received incomplete header");
|
||||
|
||||
var signature = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 0));
|
||||
var length = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 4));
|
||||
var type = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 8));
|
||||
var buffer = new byte[length];
|
||||
ns.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (signature != Signature)
|
||||
throw new ProtocolViolationException("Received packet with invalid signature");
|
||||
|
||||
RPCServerMessage packet;
|
||||
|
||||
using (var ms = new MemoryStream(buffer))
|
||||
{
|
||||
var types = Assembly.GetExecutingAssembly().GetTypes().Where(
|
||||
t =>
|
||||
t.IsSubclassOf(typeof (RPCServerMessage))
|
||||
&&
|
||||
((PacketAttribute) t.GetCustomAttributes(typeof (PacketAttribute), false).Single()).Type == type
|
||||
).ToArray();
|
||||
if (!types.Any())
|
||||
{
|
||||
throw new ProtocolViolationException("Received packet of unknown type");
|
||||
}
|
||||
if (types.Count() > 1)
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.Fail(string.Format("Bug in program code: Found more than 1 type for packet ID {0}", type));
|
||||
#else
|
||||
// TODO: log4net
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
packet = (RPCServerMessage)ProtoBuf.Serializer.NonGeneric.Deserialize(
|
||||
types.Single(),
|
||||
ms
|
||||
);
|
||||
}
|
||||
|
||||
packet.MessageId = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToUInt32(header, 12));
|
||||
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[ProtoContract]
|
||||
[Packet(1101)]
|
||||
class StorageGetPublisherFileMessage : RPCClientMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string FileName { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1102)]
|
||||
[ProtoContract]
|
||||
class StorageGetUserFileMessage : RPCClientMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public ulong NPID { get; set; } // SERIOUSLY WHY IS THIS EVEN HERE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1111)]
|
||||
[ProtoContract]
|
||||
class StoragePublisherFileMessage : RPCServerMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int Result { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public byte[] FileData { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1104)]
|
||||
[ProtoContract]
|
||||
class StorageSendRandomStringMessage : RPCClientMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string RandomString { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1112)]
|
||||
[ProtoContract]
|
||||
class StorageUserFileMessage : RPCServerMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int Result { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public ulong NPID { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public byte[] FileData { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1103)]
|
||||
[ProtoContract]
|
||||
class StorageWriteUserFileMessage : RPCClientMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public ulong NPID { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public byte[] FileData { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace NPSharp.RPC.Packets
|
||||
{
|
||||
[Packet(1113)]
|
||||
[ProtoContract]
|
||||
class StorageWriteUserFileResultMessage : RPCServerMessage
|
||||
{
|
||||
[ProtoMember(1)]
|
||||
public int Result { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public ulong NPID { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using NPSharp.RPC.Packets;
|
||||
|
||||
namespace NPSharp.RPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a low-level client stream which can communicate with an NP server using RPC packets.
|
||||
/// </summary>
|
||||
public class RPCClientStream
|
||||
{
|
||||
private NetworkStream _ns;
|
||||
private uint _id;
|
||||
|
||||
private readonly string _host;
|
||||
private readonly ushort _port;
|
||||
|
||||
private readonly Dictionary<uint, Action<RPCServerMessage>> _callbacks = new Dictionary<uint, Action<RPCServerMessage>>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an RPC connection stream with a specified host and port.
|
||||
/// </summary>
|
||||
/// <param name="host">The host to connect to.</param>
|
||||
/// <param name="port">The port to use. Default: 3025.</param>
|
||||
public RPCClientStream(string host, ushort port = 3025)
|
||||
{
|
||||
_host = host;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the RPC stream to the NP server.
|
||||
/// </summary>
|
||||
/// <returns>True if the connection succeeded, otherwise false.</returns>
|
||||
public bool Open()
|
||||
{
|
||||
// Connection already established?
|
||||
if (_ns != null)
|
||||
throw new InvalidOperationException("Connection already opened");
|
||||
|
||||
var tcp = new TcpClient();
|
||||
try
|
||||
{
|
||||
tcp.Connect(_host, _port);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_ns = tcp.GetStream();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection with the NP server.
|
||||
/// </summary>
|
||||
/// <param name="timeout"></param>
|
||||
public void Close(int timeout = 2000)
|
||||
{
|
||||
// Connection already closed?
|
||||
if (_ns == null)
|
||||
throw new InvalidOperationException("Connection already closed");
|
||||
|
||||
try
|
||||
{
|
||||
_ns.Close(timeout);
|
||||
_ns.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ns = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a callback to the next message being sent out. This allows handling response packets.
|
||||
/// </summary>
|
||||
/// <param name="callback">The method to call when we receive a response to the next message</param>
|
||||
public void AttachCallback(Action<RPCServerMessage> callback)
|
||||
{
|
||||
if (_callbacks.ContainsKey(_id))
|
||||
throw new Exception("There is already a callback for the current message. You can only add max. one callback.");
|
||||
_callbacks.Add(_id, callback);
|
||||
}
|
||||
|
||||
// TODO: Exposure of message ID needed or no?
|
||||
/// <summary>
|
||||
/// Sends out an RPC message.
|
||||
/// </summary>
|
||||
/// <param name="message">The RPC message to send out.</param>
|
||||
/// <returns>The new ID of the message.</returns>
|
||||
public uint Send(RPCClientMessage message)
|
||||
{
|
||||
if (_ns == null)
|
||||
throw new InvalidOperationException("You need to open the stream first.");
|
||||
|
||||
var buffer = message.Serialize(_id);
|
||||
|
||||
_ns.Write(buffer, 0, buffer.Length);
|
||||
|
||||
return _id++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the next RPC message from the server and reads it.
|
||||
/// </summary>
|
||||
/// <returns>The received server message.</returns>
|
||||
public RPCServerMessage Read()
|
||||
{
|
||||
if (_ns == null)
|
||||
throw new InvalidOperationException("You need to open the stream first.");
|
||||
|
||||
var message = RPCServerMessage.Deserialize(_ns);
|
||||
|
||||
if (message == null)
|
||||
return null;
|
||||
|
||||
if (!_callbacks.ContainsKey(message.MessageId))
|
||||
return message;
|
||||
|
||||
_callbacks[message.MessageId].Invoke(message);
|
||||
_callbacks.Remove(message.MessageId);
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue