using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using Tango.BL;
using Tango.BL.Entities;
using Tango.Integration.Operation;
using Tango.Logging;
using Tango.PMR;
using Tango.PMR.Common;
using Tango.PMR.Connection;
using Tango.PMR.Integration;
using Tango.PMR.MachineStatus;
using Tango.Settings;
using Tango.Transport;
using Tango.Transport.Adapters;
using Tango.Transport.Transporters;
namespace Tango.Integration.ExternalBridge
{
///
/// Represents a secure external bridge TCP client.
///
///
///
public class ExternalBridgeTcpClient : MachineOperator, IExternalBridgeSecureClient
{
private bool _logs_sent;
public event EventHandler ApplicationLogAvailable;
#region Properties
private String _serialNumber;
///
/// Gets the machine serial number.
///
public String SerialNumber
{
get { return _serialNumber; }
set
{
_serialNumber = value;
RaisePropertyChangedAuto();
}
}
private String _ipAddress;
///
/// Gets or sets the machine IP address.
///
public String IPAddress
{
get { return _ipAddress; }
set { _ipAddress = value; RaisePropertyChangedAuto(); }
}
private bool _enableApplicationLogs;
///
/// Gets or sets a value indicating whether to enable receiving application logs.
///
public bool EnableApplicationLogs
{
get { return _enableApplicationLogs; }
set
{
_enableApplicationLogs = value;
RaisePropertyChangedAuto();
OnEnableApplicationLogsChanged(value);
}
}
public bool InjectApplicationLogsToDefaultLogManager { get; set; } = true;
///
/// Gets a value indicating whether this client requires authentication.
///
public bool RequiresAuthentication => true;
///
/// Gets or sets the login request message when using .
///
public ExternalBridgeLoginRequest LoginRequest { get; set; }
///
/// Gets or sets the configure protocol request message when using .
///
public ConfigureProtocolRequest ConfigureProtocolRequest { get; set; }
///
/// Gets or sets a value indicating to apply the even if there is an error with the request.
///
public bool ForceProtocolConfiguration { get; set; }
private ApplicationInformation _applicationInformation;
///
/// Gets or sets the remote application information (PPC).
///
public ApplicationInformation ApplicationInformation
{
get { return _applicationInformation; }
protected set { _applicationInformation = value; RaisePropertyChangedAuto(); }
}
#endregion
///
/// Connects to a remote external bridge service using the specified .
///
/// The login.
///
/// The machine password is invalid.
public override Task Connect()
{
if (LoginRequest == null)
{
throw new InvalidOperationException("No LoginRequest was not specified.");
}
return Connect(LoginRequest, ConfigureProtocolRequest);
}
///
/// Connects to a remote external bridge service using the specified login.
///
/// The login request.
/// Optional protocol configuration.
///
///
public virtual async Task Connect(ExternalBridgeLoginRequest login, ConfigureProtocolRequest protocol = null)
{
if (State != TransportComponentState.Connected)
{
try
{
Adapter.EnableCompression = false;
GenericProtocol = GenericMessageProtocol.Json;
await Adapter.Connect();
State = TransportComponentState.Connected;
StartThreads();
LogManager.Log($"{ComponentName}: External Bridge TCP Client Connected...");
TimeSpan? timeout = null;
if (login.RequireSafetyLevelOperations)
{
timeout = TimeSpan.FromSeconds(35);
}
var response = await SendRequest(login, new TransportRequestConfig() { ShouldLog = true, Timeout = timeout });
if (protocol != null)
{
try
{
var configureResponse = await SendRequest(protocol, new TransportRequestConfig() { ShouldLog = true });
if (configureResponse.Message.Confirmed)
{
await Task.Delay(500);
Adapter.EnableCompression = protocol.EnableCompression;
GenericProtocol = protocol.GenericProtocol;
}
}
catch (Exception ex)
{
if (ForceProtocolConfiguration)
{
LogManager.Log(ex, $"{ComponentName}: Could not configure remote machine protocol. Could be an old PPC version. (forcing protocol configuration)");
Adapter.EnableCompression = protocol.EnableCompression;
GenericProtocol = protocol.GenericProtocol;
}
else
{
LogManager.Log(ex, $"{ComponentName}: Could not configure remote machine protocol. Could be an old PPC version.");
}
}
}
ApplicationInformation = response.Message.ApplicationInformation;
SessionLogger.CreateSession();
DeviceInformation = response.Message.DeviceInformation;
if (!response.Message.Authenticated)
{
await Adapter.Disconnect();
throw new AuthenticationException(response.Container.ErrorMessage);
}
}
catch (Exception ex)
{
try
{
await Adapter.Disconnect();
}
catch { }
throw ex;
}
ApplyContinuousChannelsConfiguration();
}
}
protected virtual void ApplyContinuousChannelsConfiguration()
{
OnEnableDiagnosticsChanged(EnableDiagnostics);
OnEnableEmbeddedDebuggingChanged(EnableEmbeddedDebugging);
OnEnableEventsNotification(EnableEventsNotification);
OnEnableApplicationLogsChanged(EnableApplicationLogs);
OnEnableMachineStatusUpdatesChanged(EnableMachineStatusUpdates);
OnEnableInkFillingStatus(EnableInkFillingStatus);
//TODO: Uncomment this only when Machine Studio enables automatic thread loading (ExternalBridgeTCPClient).
//OnEnableAutomaticThreadLoadingChanged(EnableAutomaticThreadLoading);
}
protected async void OnEnableApplicationLogsChanged(bool value)
{
if (value && State == TransportComponentState.Connected && !_logs_sent)
{
var request = new StartApplicationLogsRequest();
bool responseLogged = false;
_logs_sent = true;
SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler())
.Subscribe
(
(response) =>
{
if (!responseLogged)
{
responseLogged = true;
}
OnApplicationLogAvailable(response);
},
(ex) =>
{
_logs_sent = false;
},
() =>
{
_logs_sent = false;
});
}
else if (_logs_sent)
{
_logs_sent = false;
if (State == TransportComponentState.Connected)
{
var req = new StopApplicationLogsRequest();
try
{
var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true });
}
catch { }
}
}
}
private void OnApplicationLogAvailable(TangoMessage response)
{
try
{
if (response.Message.LogItem.Count() > 0)
{
LogItemBase log = LogItemBase.Deserialize(response.Message.LogItem.ToArray());
log.LogObject = "External Bridge";
if (InjectApplicationLogsToDefaultLogManager)
{
LogManager.Log(log);
}
ApplicationLogAvailable?.Invoke(this, log);
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error deserializing incoming application log item!");
}
}
public async override Task Disconnect()
{
if (State == TransportComponentState.Connected)
{
ExternalBridgeLogoutRequest request = new ExternalBridgeLogoutRequest();
try
{
var response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true });
}
catch { }
Status = MachineStatuses.Standby;
}
State = TransportComponentState.Disconnected;
NotifyContinuousRequestMessagesDisconnection();
SessionLogger.EndSession();
if (Adapter != null)
{
await Adapter.Disconnect();
}
LogManager.Log($"{ComponentName} disconnected.");
}
internal ExternalBridgeTcpClient()
{
}
///
/// Initializes a new instance of the class.
///
/// The machine serial number.
/// The machine IP address.
public ExternalBridgeTcpClient(String serialNumber, String ipAddress)
{
ComponentName = $"External Bridge TCP Client {_component_counter++}";
SerialNumber = serialNumber;
if (ObservablesStaticCollections.Instance.IsInitialized)
{
Machine = ObservablesStaticCollections.Instance.Machines.SingleOrDefault(x => x.SerialNumber == serialNumber);
}
IPAddress = ipAddress;
KeepAliveTimeout = TimeSpan.FromSeconds(5);
KeepAliveRetries = 2;
UseKeepAlive = false;
EnableDiagnostics = true;
Adapter = new TcpTransportAdapter(IPAddress, SettingsManager.Default.GetOrCreate().ExternalBridgeServicePort);
}
public ExternalBridgeTcpClient(Machine machine, String ipAddress)
{
ComponentName = $"External Bridge TCP Client {_component_counter++}";
Machine = machine;
SerialNumber = Machine.SerialNumber;
IPAddress = ipAddress;
KeepAliveTimeout = TimeSpan.FromSeconds(5);
KeepAliveRetries = 2;
UseKeepAlive = false;
EnableDiagnostics = true;
Adapter = new TcpTransportAdapter(IPAddress, SettingsManager.Default.GetOrCreate().ExternalBridgeServicePort);
}
///
/// Returns a that represents this instance.
///
///
/// A that represents this instance.
///
public override string ToString()
{
return SerialNumber;
}
///
/// Called when a new request has been received.
///
/// The request.
protected async override void OnRequestReceived(RequestReceivedEventArgs e)
{
base.OnRequestReceived(e);
var container = e.Container;
if (container.Type == MessageType.ExternalBridgeLogoutRequest)
{
try
{
await SendResponse(new ExternalBridgeLogoutResponse(), container.Token);
}
catch { }
await Task.Delay(2000);
try
{
State = TransportComponentState.Disconnected;
if (Adapter != null)
{
await Adapter.Disconnect();
}
LogManager.Log("External Bridge TCP client disconnected by the remote host.");
}
catch { }
SessionLogger.EndSession();
SessionClosed?.Invoke(this, new EventArgs());
NotifyContinuousRequestMessagesDisconnection();
}
}
protected override void OnMachineStateChanged(MachineState state)
{
//Do Nothing...
}
///
/// Occurs when the remote host has closed the session.
///
public event EventHandler SessionClosed;
///
/// Gets the database machine associated with this client.
///
public Machine Machine { get; protected set; }
///
/// Sets the database machine.
///
/// The machine.
public void SetMachine(Machine machine)
{
Machine = machine;
}
}
}