323 lines
No EOL
11 KiB
C#
323 lines
No EOL
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using JD.Interfaces.Logging;
|
|
using JD.Interfaces.Remoting;
|
|
using JD.Remoting.Connections;
|
|
using JD.Remoting.Details;
|
|
using JD.Remoting.Exceptions;
|
|
using JetBrains.Annotations;
|
|
using Plume.Core.Logging;
|
|
|
|
namespace JD.Remoting
|
|
{
|
|
/// <inheritdoc />
|
|
/// <summary>
|
|
/// A plugin is the High Level entity responsible of handling commands in order to communicate with its counterpat on engine side.
|
|
/// </summary>
|
|
public class Plugin : IPlugin
|
|
{
|
|
#region Variables
|
|
|
|
private readonly IConnection _connection;
|
|
private readonly HandlerDictionary _handlers = new HandlerDictionary();
|
|
private readonly QueryDictionary _queries = new QueryDictionary();
|
|
private readonly RemotingLogger _logger;
|
|
private readonly List<EventHandler> _updateHandlers = new List<EventHandler>();
|
|
private Action _reportQueryTimeout = () => { };
|
|
private bool _allowConnectionToIncompatibleTargets;
|
|
private bool _allowConnectionToDefaultTarget = true;
|
|
private bool _connectedToIncompatibleTarget;
|
|
private bool _connectedToDefaultTarget;
|
|
private bool _allowConnectionToNextTarget;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public Plugin(string name, ILogger logger, IConnection connection = null)
|
|
{
|
|
_logger = logger != null ? new RemotingLogger(logger) : new RemotingLogger();
|
|
Name = name;
|
|
_connection = connection ?? new TcpClientConnection(this);
|
|
_connection.Received += connection_Received;
|
|
_connection.ConnectedChanged += connection_ConnectedChanged;
|
|
_connection.ConnectedToIncompatibleTarget += connection_ConnectedToIncompatibleTarget;
|
|
_connection.ConnectedToDefaultTarget += _connection_ConnectedToDefaultTarget;
|
|
}
|
|
public Plugin(string name, IConnection connection = null) : this(name, null, connection)
|
|
{
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
UnregisterUpdateHandlers();
|
|
_connection.Close();
|
|
var disposable = _connection as IDisposable;
|
|
disposable?.Dispose();
|
|
|
|
Disposed?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public string Name { get; }
|
|
|
|
/// <summary>
|
|
/// How to dispatch actions on the main thread.
|
|
/// </summary>
|
|
public Action<Action> DispatchOnMainThread
|
|
{
|
|
get { return _handlers.DispatchOnMainThread; }
|
|
set { _handlers.DispatchOnMainThread = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// What to do when the engine does not answer the query in the specified amount of time.
|
|
/// </summary>
|
|
public Action ReportQueryTimeout
|
|
{
|
|
get { return _reportQueryTimeout; }
|
|
set { _reportQueryTimeout = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Starts the plugin, from that point, it will try to auto connect to the engine.
|
|
/// </summary>
|
|
public void Start(IEndPointId id = null)
|
|
{
|
|
if (_connection.IsConnected)
|
|
{
|
|
_connection.Close();
|
|
}
|
|
_connection.Open(id ?? EndPointId.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops to listen/send commands.
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
_connection.Close();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Commands
|
|
|
|
public bool IsConnected
|
|
{
|
|
get { return _connection.IsConnected; }
|
|
}
|
|
|
|
public bool AllowConnectionToIncompatibleTarget
|
|
{
|
|
get { return _allowConnectionToIncompatibleTargets; }
|
|
set { _allowConnectionToIncompatibleTargets = value; }
|
|
}
|
|
|
|
public bool AllowConnectionToDefaultTarget
|
|
{
|
|
get { return _allowConnectionToDefaultTarget; }
|
|
set { _allowConnectionToDefaultTarget = value; }
|
|
}
|
|
|
|
public bool AllowConnectionToNextHost
|
|
{
|
|
get { return _allowConnectionToNextTarget; }
|
|
set { _allowConnectionToNextTarget = value; }
|
|
}
|
|
|
|
public string ProjectId { get { return _connection.ProjectId; } }
|
|
|
|
/// <summary>
|
|
/// Sends a message to the host connected.
|
|
/// </summary>
|
|
/// <returns>0 if operation succeeded, a non-zero value otherwise.
|
|
/// See Windows Sockets Error Codes page for more information on error codes. </returns>
|
|
/// <exception cref="System.ObjectDisposedException">The System.Net.Socket has been closed. </exception>
|
|
public int Send(IMessage message)
|
|
{
|
|
_logger.Sent(message);
|
|
if (!IsConnected)
|
|
{
|
|
return -1;
|
|
}
|
|
return _connection.Send(message);
|
|
}
|
|
|
|
public void RegisterHandler(string commandName, Action<IBlob> handler, bool dispatchOnMainThread = true)
|
|
{
|
|
_handlers.Register(commandName, handler, dispatchOnMainThread);
|
|
}
|
|
|
|
public void UnregisterHandler(string commandName, Action<IBlob> handler)
|
|
{
|
|
_handlers.Unregister(commandName, handler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an update handler.
|
|
/// </summary>
|
|
/// <param name="handler"></param>
|
|
public void RegisterUpdateHandler(EventHandler handler)
|
|
{
|
|
if (_updateHandlers.IndexOf(handler) == -1)
|
|
{
|
|
_updateHandlers.Add(handler);
|
|
_connection.Update += handler;
|
|
}
|
|
}
|
|
|
|
public void UnregisterUpdateHandler(EventHandler handler)
|
|
{
|
|
if (_updateHandlers.IndexOf(handler) != -1)
|
|
{
|
|
_updateHandlers.Remove(handler);
|
|
_connection.Update -= handler;
|
|
}
|
|
}
|
|
|
|
public void UnregisterUpdateHandlers()
|
|
{
|
|
foreach (EventHandler handler in _updateHandlers)
|
|
{
|
|
_connection.Update -= handler;
|
|
}
|
|
|
|
_updateHandlers.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Queries
|
|
|
|
/// <summary>
|
|
/// Queries the engine for a set of data using the specified command. Parse the blob, and call the handler with the final data.
|
|
/// </summary>
|
|
public IQueryResult<TData> Query<TData>(string queryName, Converter<IBlob, TData> resultParser)
|
|
{
|
|
return Query(queryName, null, queryName, resultParser);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queries the engine for a set of data using the specified command. Parse the blob, and call the handler with the final data.
|
|
/// </summary>
|
|
public IQueryResult<TData> Query<TData>(string queryName, Action<IBlob> writeCommand, string responseName, Converter<IBlob, TData> resultParser)
|
|
{
|
|
var result = new QueryResult<TData>(ReportQueryTimeout);
|
|
_queries.Register(responseName, blob =>
|
|
{
|
|
result.Content = resultParser(blob);
|
|
});
|
|
|
|
_connection.Send(queryName, writeCommand);
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler Disposed;
|
|
public event EventHandler<IPlugin> ConnectedChanged;
|
|
protected virtual void OnConnectedChanged(IPlugin e)
|
|
{
|
|
ConnectedChanged?.Invoke(this, e);
|
|
}
|
|
|
|
public event System.ComponentModel.CancelEventHandler ConnectedToIncompatibleTarget;
|
|
protected virtual void OnConnectedToIncompatibleTarget(System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
ConnectedToIncompatibleTarget?.Invoke(this, e);
|
|
}
|
|
|
|
public event System.ComponentModel.CancelEventHandler ConnectedToDefaultTarget;
|
|
protected virtual void OnConnectedToDefaultTarget(System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
ConnectedToDefaultTarget?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sent when a remoting exception occurs during the handling of a message in the application side.
|
|
/// Set the Cancel property of the argument to true if you have recovered from the issue.
|
|
/// <remarks>
|
|
/// This event is meant as a last chance safety measure to prevent application crash and give the application a chance to restore
|
|
/// its state. It should not be used to handle mundane errors.
|
|
/// </remarks>
|
|
/// </summary>
|
|
public event System.ComponentModel.CancelEventHandler RemotingExceptionReceived;
|
|
protected virtual void OnRemotingExceptionReceived(System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
RemotingExceptionReceived?.Invoke(this, e);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Handlers
|
|
|
|
private void connection_Received(object sender, IMessageReceivedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
bool handled = _queries.Handle(e.Message) | _handlers.Handle(e.Message);
|
|
_logger.Received(e.Message, handled);
|
|
}
|
|
catch (RemotingException ex)
|
|
{
|
|
// Last chance to save the application.
|
|
System.ComponentModel.CancelEventArgs args = new System.ComponentModel.CancelEventArgs();
|
|
OnRemotingExceptionReceived(args);
|
|
|
|
_logger.ExceptionReceived(e.Message, ex, args.Cancel);
|
|
if(!args.Cancel)
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void connection_ConnectedChanged(object sender, IPlugin e)
|
|
{
|
|
OnConnectedChanged(e);
|
|
|
|
if (!_allowConnectionToDefaultTarget && _connectedToDefaultTarget && !IsConnected)
|
|
{
|
|
// Nothing to do?
|
|
}
|
|
|
|
if (!_allowConnectionToIncompatibleTargets && _connectedToIncompatibleTarget && !IsConnected)
|
|
{
|
|
if (_allowConnectionToNextTarget)
|
|
{
|
|
Stop();
|
|
_connectedToIncompatibleTarget = false;
|
|
|
|
IEndPointId nextEndPointId = _connection.EndPoint.GetNextPort();
|
|
Start(nextEndPointId);
|
|
}
|
|
}
|
|
}
|
|
|
|
void connection_ConnectedToIncompatibleTarget(object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
OnConnectedToIncompatibleTarget(e);
|
|
|
|
e.Cancel = !_allowConnectionToIncompatibleTargets;
|
|
_connectedToIncompatibleTarget = true;
|
|
}
|
|
|
|
void _connection_ConnectedToDefaultTarget(object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
OnConnectedToDefaultTarget(e);
|
|
|
|
e.Cancel = !_allowConnectionToDefaultTarget;
|
|
_connectedToDefaultTarget = true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |