JD2022-TU1/main/tools/framework/JD.Remoting/Plugin.cs

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
}
}