using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.BL;
using Tango.BL.Entities;
using Tango.Core;
using Tango.Core.DI;
using Tango.Core.ExtensionMethods;
using Tango.Core.Threading;
using Tango.Emulations.ExternalBridge;
using Tango.FSE.BL;
using Tango.FSE.Common;
using Tango.FSE.Common.Authentication;
using Tango.FSE.Common.Build;
using Tango.FSE.Common.Connection;
using Tango.FSE.Common.FSEApplication;
using Tango.FSE.Common.Notifications;
using Tango.FSE.UI.Dialogs;
using Tango.Integration.ExternalBridge;
using Tango.Integration.Operation;
using Tango.Logging;
using Tango.PMR.Integration;
using Tango.SharedUI;
using Tango.Transport;
namespace Tango.FSE.UI.Connection
{
///
/// Represents the default implementation.
///
///
///
public class DefaultMachineProvider : FSEExtendedObject, IMachineProvider
{
private List _eventRegistrations;
private String _lastMachineSerialNumber;
private DateTime _connectionStartTime;
private System.Timers.Timer _connectionTimer;
private FSEMachineEventsStateProvider _machineEventsStateProvider;
private bool _autoReconnect;
[TangoInject]
private INotificationProvider NotificationProvider { get; set; }
[TangoInject]
private IAuthenticationProvider AuthenticationProvider { get; set; }
[TangoInject]
private FSEServicesContainer Services { get; set; }
[TangoInject]
private IBuildProvider BuildProvider { get; set; }
#region Events
///
/// Occurs when machine operator has connected.
///
public event EventHandler MachineConnected;
///
/// Occurs when the machine operator has disconnected.
///
public event EventHandler MachineDisconnected;
#endregion
#region Properties
private Machine _machine;
///
/// Gets the database machine entity associated with the current machine.
///
public Machine Machine
{
get { return _machine; }
private set { _machine = value; RaisePropertyChangedAuto(); }
}
private bool _isConnected;
///
/// Gets or sets a value indicating whether a machine is currently connected.
///
public bool IsConnected
{
get { return _isConnected; }
private set { _isConnected = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(ConnectionType)); RaisePropertyChanged(nameof(IsPPCAvailable)); }
}
///
/// Gets the current connected machine connection type.
///
public MachineConnectionTypes ConnectionType
{
get
{
if (MachineOperator.GetType() == typeof(ExternalBridgeUsbClient))
{
return MachineConnectionTypes.USB;
}
else if (MachineOperator.GetType() == typeof(ExternalBridgeTcpClient))
{
return MachineConnectionTypes.Wifi;
}
else if (MachineOperator.GetType() == typeof(ExternalBridgeSignalRClient))
{
return MachineConnectionTypes.SignalR;
}
else
{
return MachineConnectionTypes.Emulator;
}
}
}
///
/// Gets a value indicating whether the equals or .
///
public bool IsPPCAvailable
{
get { return IsConnected && ConnectionType.IsRemote(); }
}
private TimeSpan _connectionTime;
///
/// Gets the connection time span.
///
public TimeSpan ConnectionTime
{
get { return _connectionTime; }
set { _connectionTime = value; RaisePropertyChangedAuto(); }
}
private bool _isBusy;
///
/// Gets a value indicating whether this instance is busy connecting/disconnecting.
///
public bool IsBusy
{
get { return _isBusy; }
private set { _isBusy = value; RaisePropertyChangedAuto(); }
}
private IExternalBridgeClient _machineOperator;
///
/// Gets the machine operator.
///
public IExternalBridgeClient MachineOperator
{
get { return _machineOperator; }
private set { _machineOperator = value; RaisePropertyChangedAuto(); }
}
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public DefaultMachineProvider() : base()
{
_machineEventsStateProvider = new FSEMachineEventsStateProvider();
MachineOperator = new ExternalBridgeTcpClient("N/A", "N/A");
MachineOperator.MachineEventsStateProvider = _machineEventsStateProvider;
MachineOperator.StateChanged += MachineOperator_StateChanged;
MachineOperator.As().SessionClosed += DefaultMachineProvider_SessionClosed;
_connectionTimer = new System.Timers.Timer(1000);
_connectionTimer.Elapsed += _connectionTimer_Elapsed;
_connectionTimer.Start();
}
#endregion
#region Public Methods
///
/// Connects to the specified machine.
///
/// The machine.
///
public async Task ConnectToMachine(IExternalBridgeClient machine)
{
try
{
_autoReconnect = false;
LogManager.Log($"Connecting to machine '{machine.GetType().Name}' => '{machine.SerialNumber}' => '{machine.Adapter?.Address}'...");
IsBusy = true;
MachineConnectionBaseViewVM vm = null;
LogManager.Log("Invoking machine connection dialog...");
if (machine.GetType() == typeof(ExternalBridgeUsbClient))
{
vm = await NotificationProvider.ShowDialog(new MachineConnectionUsbViewVM() { Machine = machine });
}
else if (machine.GetType() == typeof(EmulatorExternalBridge))
{
vm = await NotificationProvider.ShowDialog(new MachineConnectionEmulatorViewVM() { Machine = machine });
}
else if (machine is ExternalBridgeTcpClient)
{
var secureClient = machine.As();
if (machine.GetType() == typeof(ExternalBridgeTcpClient))
{
vm = await NotificationProvider.ShowDialog(new MachineConnectionWifiViewVM(secureClient));
_autoReconnect = (vm as MachineConnectionWifiViewVM).AutoReconnection;
}
else
{
vm = await NotificationProvider.ShowDialog(new MachineConnectionSignalRViewVM(secureClient));
}
secureClient.LoginRequest = new ExternalBridgeLoginRequest()
{
AppID = BuildProvider.BuildName,
HostName = Environment.MachineName,
Password = (vm as MachineConnectionWifiViewVM).Password,
Intent = (vm as MachineConnectionWifiViewVM).SelectedIntent,
RequireSafetyLevelOperations = (vm as MachineConnectionWifiViewVM).RequireSafetyLevelOperations,
UserGuid = AuthenticationProvider.CurrentUser.Guid,
UserName = AuthenticationProvider.CurrentUser.Contact.FullName
};
secureClient.ForceProtocolConfiguration = Settings.ForceExternalBridgeProtocolConfiguration;
secureClient.ConfigureProtocolRequest = new ConfigureProtocolRequest()
{
EnableCompression = true,
GenericProtocol = GenericMessageProtocol.Bson
};
LogManager.Log($"Target machine requires a secure connection. Login Request:\n{secureClient.LoginRequest.ToJsonString()}");
}
else
{
throw LogManager.Log(new NotSupportedException($"The specified type {machine.GetType().Name} is not supported."));
}
if (vm.DialogResult)
{
if (IsConnected)
{
LogManager.Log("Another machine is currently connected. Disconnecting current machine...");
await DisconnectMachine();
}
IsBusy = true;
String serial = vm.GetMachineSerialNumber();
machine.SerialNumber = serial;
using (var task = NotificationProvider.PushTaskItem($"Connecting to machine '{serial}'..."))
{
LogManager.Log("Retrieving full machine details...");
var machineDB = await Services.MachinesService.GetMachineFull(serial);
if (machineDB == null)
{
throw LogManager.Log(new KeyNotFoundException("The specified machine was not found on the database."));
}
LogManager.Log("Reassigning previous machine operator event handlers...");
ReassignEventHandlers(MachineOperator, machine);
MachineOperator = machine;
ApplyOperatorSettings();
LogManager.Log("Connecting...");
await MachineOperator.Connect();
Machine = machineDB;
LogManager.Log("Machine successfully connected.");
IsConnected = true;
OnMachineConnected(MachineOperator);
if (machine.GetType() == typeof(ExternalBridgeUsbClient) || machine.GetType() == typeof(EmulatorExternalBridge))
{
LogManager.Log($"Connected machine is of type '{machine.GetType()}' requires uploading hardware configuration upon connection.");
task.UpdateProgress("Uploading hardware configuration...");
await Task.Delay(1000);
try
{
LogManager.Log("Uploading hardware configuration...");
await machine.UploadHardwareConfiguration(Machine.Configuration.HardwareVersion, Machine.Configuration);
}
catch (Exception ex)
{
LogManager.Log(ex, "Error uploading hardware configuration. The connection will remain but in an unsafe state.");
await NotificationProvider.ShowWarning($"Error uploading hardware configuration.\n{ex.FlattenMessage()}");
}
}
return true;
}
}
else
{
return false;
}
}
catch
{
throw;
}
finally
{
IsBusy = false;
}
}
///
/// Disconnects the current connected machine.
///
///
public async Task DisconnectMachine()
{
if (IsConnected)
{
LogManager.Log($"Disconnecting currently connected machine...");
using (var task = NotificationProvider.PushTaskItem("Disconnecting from current machine..."))
{
try
{
await Task.Delay(1500);
IsBusy = true;
await MachineOperator.Disconnect();
}
catch (Exception ex)
{
LogManager.Log(ex, "Error disconnecting machine.");
}
finally
{
IsConnected = false;
IsBusy = false;
Machine = null;
OnMachineDisconnected(MachineOperator, null);
}
}
}
}
///
/// Disconnects the currently connected machine and displays a "waiting for reconnection dialog" with a timeout.
/// Useful when doing remote application restart.
///
/// The amount of time to wait before starting reconnection attempts.
/// The timeout for when to drop the reconnection attempt.
/// The message to display to the user
///
public async Task DisconnectAndWaitForReconnection(TimeSpan beginDelay, TimeSpan timeout, String message = null)
{
IsBusy = true;
if (!_autoReconnect)
{
LogManager.Log("Starting disconnect and wait for machine procedure...");
LogManager.Log("Disconnecting from current machine...");
try
{
IsConnected = false;
await MachineOperator.Disconnect();
LogManager.Log("Machine disconnected.");
}
catch (Exception ex)
{
LogManager.Log(ex, "Error disconnecting machine. Never mind...");
}
finally
{
OnMachineDisconnected(MachineOperator, null);
}
}
bool aborted = false;
MachineWaitForConnectionViewVM vm = new MachineWaitForConnectionViewVM(MachineOperator, (int)timeout.TotalSeconds, message);
vm.RequestCancel += (_, __) =>
{
vm.BlockCancel = true;
aborted = true;
};
ThreadFactory.StartNew(() =>
{
Thread.Sleep(beginDelay);
while (!aborted)
{
LogManager.Log("Trying to reconnect (controlled disconnection)...");
try
{
MachineOperator.Connect().Wait();
IsConnected = true;
OnMachineConnected(MachineOperator);
LogManager.Log("Machine reconnected successfully. Closing dialog...");
InvokeUI(() =>
{
vm.Close(false);
});
break;
}
catch (Exception ex)
{
LogManager.Log(ex, "Error reconnecting to machine. Maybe next time...");
}
if (aborted) break;
Thread.Sleep(10000); //Try every 10 seconds..
}
if (!IsConnected)
{
InvokeUI(() =>
{
vm.Close(false);
});
}
});
System.Timers.Timer connectionTimer = new System.Timers.Timer();
connectionTimer.Interval = 1000;
connectionTimer.Elapsed += (_, __) =>
{
vm.RemainingSeconds--;
if (vm.RemainingSeconds == 0 || IsConnected || aborted)
{
vm.BlockCancel = true;
aborted = true;
connectionTimer.Stop();
connectionTimer.Dispose();
}
};
connectionTimer.Start();
await NotificationProvider.ShowDialog(vm);
aborted = true;
IsBusy = false;
bool abortedByUser = vm.RemainingSeconds > 0;
if (!IsConnected)
{
if (abortedByUser)
{
LogManager.Log("Auto reconnection aborted by user.");
}
else
{
LogManager.Log("Failed to auto-reconnect to the last machine (controlled disconnection).", LogCategory.Error);
await NotificationProvider.ShowError($"Failed to reconnect to the last connected machine.");
}
}
}
#endregion
#region Machine Operator Event Handlers
private void MachineOperator_StateChanged(object sender, TransportComponentState state)
{
if (IsConnected && state == TransportComponentState.Failed)
{
IsConnected = false;
LogManager.Log($"Current machine connection has failed and disconnected after a period of {ConnectionTime} with exception: {MachineOperator.FailedStateException.FlattenMessage()}");
LogManager.Log($"Trying reconnection procedure within {Settings.AutoMachineReconnectionTimeoutSeconds} seconds...");
OnMachineDisconnected(MachineOperator, MachineOperator.FailedStateException);
if (_autoReconnect) return;
InvokeUI(async () =>
{
var vm = await NotificationProvider.ShowDialog(new MachineConnectionLostViewVM(MachineOperator, MachineOperator.FailedStateException.FlattenMessage(), Settings.AutoMachineReconnectionTimeoutSeconds));
if (vm.DialogResult)
{
try
{
LogManager.Log("Auto reconnection dialog accepted...");
IsBusy = true;
using (var task = NotificationProvider.PushTaskItem($"Reconnecting to machine '{Machine.SerialNumber}'..."))
{
await Task.Delay(2000);
await MachineOperator.Connect();
IsConnected = true;
OnMachineConnected(MachineOperator);
LogManager.Log("Machine reconnected successfully.");
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Failed to reconnect to the last machine.");
await NotificationProvider.ShowError($"Failed to reconnect to the last connected machine.\n{ex.FlattenMessage()}");
}
finally
{
IsBusy = false;
}
}
else
{
LogManager.Log("Auto reconnection dialog canceled. Reconnection aborted.");
}
});
}
}
private void DefaultMachineProvider_SessionClosed(object sender, EventArgs e)
{
IsConnected = false;
String message = "The connection has been terminated by the remote machine.";
LogManager.Log(message);
OnMachineDisconnected(MachineOperator, new Exception(message));
InvokeUI(() =>
{
NotificationProvider.ShowError(message);
});
}
#endregion
#region Event Handlers
private void _connectionTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (IsConnected)
{
ConnectionTime = DateTime.Now - _connectionStartTime;
}
}
#endregion
#region Virtual Methods
protected virtual void OnMachineConnected(IExternalBridgeClient machineOperator)
{
Settings.LastConnectedMachineType = (int)machineOperator.MachineType;
MachineConnected?.Invoke(this, new MachineConnectedEventArgs()
{
MachineOperator = machineOperator,
DifferentFromPrevious = machineOperator.SerialNumber != _lastMachineSerialNumber,
});
_lastMachineSerialNumber = machineOperator.SerialNumber;
_connectionStartTime = DateTime.Now;
}
protected virtual void OnMachineDisconnected(IExternalBridgeClient machineOperator, Exception exception)
{
machineOperator?.MachineEventsStateProvider.Reset();
LogManager.Log($"Machine disconnected. Total connection time: {DateTime.Now - _connectionStartTime}.");
MachineDisconnected?.Invoke(this, new MachineDisconnectedEventArgs()
{
MachineOperator = machineOperator,
Exception = exception
});
if (_autoReconnect && exception != null)
{
LogManager.Log("Auto reconnection is activated, trying to reconnect.");
InvokeUI(async () =>
{
await DisconnectAndWaitForReconnection(TimeSpan.FromSeconds(10), TimeSpan.FromHours(1), "The connection with the remote machine has lost, trying to reconnect.");
});
}
}
#endregion
#region Private Methods
private void ApplyOperatorSettings()
{
LogManager.Log("Applying machine operator settings...");
MachineOperator.MachineEventsStateProvider = _machineEventsStateProvider;
MachineOperator.EnableDiagnostics = true;
MachineOperator.EnableMachineStatusUpdates = true;
MachineOperator.EnableEmbeddedDebugging = true;
MachineOperator.EnableEventsNotification = true;
MachineOperator.EnableJobResume = false;
MachineOperator.UseKeepAlive = true;
MachineOperator.JobUnitsMethod = JobUnitsMethods.Device;
MachineOperator.JobRunsLogger.JobSource = Tango.BL.Enumerations.JobSource.Remote;
MachineOperator.KeepAliveTimeout = TimeSpan.FromSeconds(5);
MachineOperator.KeepAliveRetries = 4;
MachineOperator.RequestTimeout = TimeSpan.FromSeconds(10);
MachineOperator.ContinuousRequestTimeout = TimeSpan.FromSeconds(10);
if (ConnectionType.IsRemote())
{
ExternalBridgeTcpClient tcpClient = MachineOperator.As();
tcpClient.EnableApplicationLogs = true;
tcpClient.InjectApplicationLogsToDefaultLogManager = false;
tcpClient.LogEmbeddedDebuggingToFile = false;
}
}
#endregion
#region Machine Operator Event & Request Handlers Reassignment
private class EventRegistration
{
public Delegate Handler { get; set; }
public EventInfo EventInfo { get; set; }
public void RemoveHandler(Object instance)
{
try
{
EventInfo.RemoveEventHandler(instance, Handler);
}
catch
{
Debug.WriteLine($"Cannot remove handler '{Handler.Method.Name}' for event '{instance.GetType().Name}.{EventInfo.Name}'.");
}
}
public void AddHandler(Object instance)
{
try
{
EventInfo.AddEventHandler(instance, Handler);
}
catch
{
Debug.WriteLine($"Cannot add handler '{Handler.Method.Name}' for event '{instance.GetType().Name}.{EventInfo.Name}'.");
}
}
}
///
/// Reassigns the event handlers from the previous connected machine to a new one.
///
/// The old instance.
/// The new instance.
///
private void ReassignEventHandlers(IMachineOperator oldInstance, IMachineOperator newInstance)
{
oldInstance.CopyRequestHandlers(newInstance);
if (_eventRegistrations == null)
{
_eventRegistrations = GetEventsRegistrations(oldInstance);
}
foreach (var registration in _eventRegistrations)
{
registration.RemoveHandler(oldInstance);
registration.AddHandler(newInstance);
}
}
private List GetEventsRegistrations(IMachineOperator instance)
{
List registrations = new List();
var instanceEvents = instance.GetType().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).ToList();
foreach (var ev in instanceEvents)
{
if (ev.IsMulticast)
{
FieldInfo eventField = null;
Type type = instance.GetType();
while (eventField == null)
{
eventField = type.GetField(ev.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
if (eventField == null)
{
type = type.BaseType;
}
if (type == typeof(Object))
{
break;
}
}
if (eventField == null)
{
throw new NullReferenceException($"Could not locate event field for '{ev.Name}'.");
}
var eventDelegate = (MulticastDelegate)type.GetField(ev.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static).GetValue(instance);
if (eventDelegate != null)
{
foreach (var handler in eventDelegate.GetInvocationList())
{
registrations.Add(new EventRegistration()
{
EventInfo = ev,
Handler = handler,
});
}
}
}
}
return registrations;
}
#endregion
#region Application Ready
///
/// Called when is ready and user is logged-in. (happens every time a user logs-in)
///
public void OnApplicationReady(IFSEApplicationManager applicationManager)
{
_machineEventsStateProvider.Init();
}
#endregion
}
}