aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Authentication
ModeNameSize
-rw-r--r--DefaultAuthenticationProvider.cs6843logstatsplain
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.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
{
    /// <summary>
    /// Represents the default <see cref="IMachineProvider"/> implementation.
    /// </summary>
    /// <seealso cref="Tango.FSE.Common.FSEExtendedObject" />
    /// <seealso cref="Tango.FSE.Common.Connection.IMachineProvider" />
    public class DefaultMachineProvider : FSEExtendedObject, IMachineProvider
    {
        private List<EventRegistration> _eventRegistrations;
        private String _lastMachineSerialNumber;
        private DateTime _connectionStartTime;
        private System.Timers.Timer _connectionTimer;
        private FSEMachineEventsStateProvider _machineEventsStateProvider;

        [TangoInject]
        private INotificationProvider NotificationProvider { get; set; }

        [TangoInject]
        private IAuthenticationProvider AuthenticationProvider { get; set; }

        [TangoInject]
        private FSEServicesContainer Services { get; set; }

        #region Events

        /// <summary>
        /// Occurs when machine operator has connected.
        /// </summary>
        public event EventHandler<MachineConnectedEventArgs> MachineConnected;

        /// <summary>
        /// Occurs when the machine operator has disconnected.
        /// </summary>
        public event EventHandler<MachineDisconnectedEventArgs> MachineDisconnected;

        #endregion

        #region Properties

        private Machine _machine;
        /// <summary>
        /// Gets the database machine entity associated with the current machine.
        /// </summary>
        public Machine Machine
        {
            get { return _machine; }
            private set { _machine = value; RaisePropertyChangedAuto(); }
        }

        private bool _isConnected;
        /// <summary>
        /// Gets or sets a value indicating whether a machine is currently connected.
        /// </summary>
        public bool IsConnected
        {
            get { return _isConnected; }
            private set { _isConnected = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(ConnectionType)); RaisePropertyChanged(nameof(IsPPCAvailable)); }
        }

        /// <summary>
        /// Gets the current connected machine connection type.
        /// </summary>
        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;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="ConnectionType" /> equals <see cref="MachineConnectionTypes.Wifi" /> or <see cref="MachineConnectionTypes.SignalR" />.
        /// </summary>
        public bool IsPPCAvailable
        {
            get { return IsConnected && ConnectionType.IsRemote(); }
        }

        private TimeSpan _connectionTime;
        /// <summary>
        /// Gets the connection time span.
        /// </summary>
        public TimeSpan ConnectionTime
        {
            get { return _connectionTime; }
            set { _connectionTime = value; RaisePropertyChangedAuto(); }
        }

        private bool _isBusy;
        /// <summary>
        /// Gets a value indicating whether this instance is busy connecting/disconnecting.
        /// </summary>
        public bool IsBusy
        {
            get { return _isBusy; }
            private set { _isBusy = value; RaisePropertyChangedAuto(); }
        }

        private IExternalBridgeClient _machineOperator;
        /// <summary>
        /// Gets the machine operator.
        /// </summary>
        public IExternalBridgeClient MachineOperator
        {
            get { return _machineOperator; }
            private set { _machineOperator = value; RaisePropertyChangedAuto(); }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultMachineProvider"/> class.
        /// </summary>
        public DefaultMachineProvider() : base()
        {
            _machineEventsStateProvider = new FSEMachineEventsStateProvider();

            MachineOperator = new ExternalBridgeTcpClient("N/A", "N/A");
            MachineOperator.MachineEventsStateProvider = _machineEventsStateProvider;
            MachineOperator.StateChanged += MachineOperator_StateChanged;
            MachineOperator.As<ExternalBridgeTcpClient>().SessionClosed += DefaultMachineProvider_SessionClosed;

            _connectionTimer = new System.Timers.Timer(1000);
            _connectionTimer.Elapsed += _connectionTimer_Elapsed;
            _connectionTimer.Start();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Connects to the specified machine.
        /// </summary>
        /// <param name="machine">The machine.</param>
        /// <returns></returns>
        public async Task<bool> ConnectToMachine(IExternalBridgeClient machine)
        {
            try
            {
                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<MachineConnectionUsbViewVM>();
                }
                else if (machine.GetType() == typeof(EmulatorExternalBridge))
                {
                    vm = await NotificationProvider.ShowDialog<MachineConnectionEmulatorViewVM>();
                }
                else if (machine is ExternalBridgeTcpClient)
                {
                    var secureClient = machine.As<ExternalBridgeTcpClient>();

                    if (machine.GetType() == typeof(ExternalBridgeTcpClient))
                    {
                        vm = await NotificationProvider.ShowDialog(new MachineConnectionWifiViewVM(secureClient));
                    }
                    else
                    {
                        vm = await NotificationProvider.ShowDialog(new MachineConnectionSignalRViewVM(secureClient));
                    }

                    secureClient.LoginRequest = new ExternalBridgeLoginRequest()
                    {
                        AppID = "Tango FSE",
                        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.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;
            }
        }

        /// <summary>
        /// Disconnects the current connected machine.
        /// </summary>
        /// <returns></returns>
        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);
                    }
                }
            }
        }

        /// <summary>
        /// Disconnects the currently connected machine and displays a "waiting for reconnection dialog" with a timeout.
        /// Useful when doing remote application restart.
        /// </summary>
        /// <param name="beginDelay">The amount of time to wait before starting reconnection attempts.</param>
        /// <param name="timeout">The timeout for when to drop the reconnection attempt.</param>
        /// <param name="message">The message to display to the user</param>
        /// <returns></returns>
        public async Task DisconnectAndWaitForReconnection(TimeSpan beginDelay, TimeSpan timeout, String message = null)
        {
            LogManager.Log("Starting disconnect and wait for machine procedure...");
            LogManager.Log("Disconnecting from current machine...");

            IsBusy = true;

            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);

                InvokeUI(async () =>
                {
                    var vm = await NotificationProvider.ShowDialog<MachineConnectionLostViewVM>(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)
        {
            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
            });
        }

        #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<ExternalBridgeTcpClient>();
                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}'.");
                }
            }
        }

        /// <summary>
        /// Reassigns the event handlers from the previous connected machine to a new one.
        /// </summary>
        /// <param name="oldInstance">The old instance.</param>
        /// <param name="newInstance">The new instance.</param>
        /// <exception cref="NullReferenceException"></exception>
        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<EventRegistration> GetEventsRegistrations(IMachineOperator instance)
        {
            List<EventRegistration> registrations = new List<EventRegistration>();

            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

        /// <summary>
        /// Called when is ready and user is logged-in. (happens every time a user logs-in)
        /// </summary>
        public void OnApplicationReady(IFSEApplicationManager applicationManager)
        {
            _machineEventsStateProvider.Init();
        }

        #endregion
    }
}