/* * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ using System.Threading; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface.MessageBox; using SafeExamBrowser.Runtime.Operations.Events; namespace SafeExamBrowser.Runtime.Operations { internal class ServiceOperation : SessionOperation { private ILogger logger; private IRuntimeHost runtimeHost; private IServiceProxy service; private int timeout_ms; public override event ActionRequiredEventHandler ActionRequired; public override event StatusChangedEventHandler StatusChanged; public ServiceOperation( ILogger logger, IRuntimeHost runtimeHost, IServiceProxy service, SessionContext sessionContext, int timeout_ms) : base(sessionContext) { this.logger = logger; this.runtimeHost = runtimeHost; this.service = service; this.timeout_ms = timeout_ms; } public override OperationResult Perform() { logger.Info($"Initializing service session..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); var success = TryEstablishConnection(); if (service.IsConnected) { success = TryStartSession(); } return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Repeat() { logger.Info($"Initializing new service session..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); var success = false; if (service.IsConnected) { success = TryStopSession(); } else { success = TryEstablishConnection(); } if (success && service.IsConnected) { success = TryStartSession(); } return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Revert() { logger.Info("Finalizing service session..."); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession); var success = true; if (service.IsConnected) { success &= TryStopSession(); success &= TryTerminateConnection(); } return success ? OperationResult.Success : OperationResult.Failed; } private bool TryEstablishConnection() { var mandatory = Context.Next.Settings.ServicePolicy == ServicePolicy.Mandatory; var warn = Context.Next.Settings.ServicePolicy == ServicePolicy.Warn; var connected = service.Connect(); var success = connected || !mandatory; if (success) { service.Ignore = !connected; logger.Info($"The service is {(mandatory ? "mandatory" : "optional")} and {(connected ? "connected." : "not connected. All service-related operations will be ignored!")}"); if (!connected && warn) { ActionRequired?.Invoke(new MessageEventArgs { Icon = MessageBoxIcon.Warning, Message = TextKey.MessageBox_ServiceUnavailableWarning, Title = TextKey.MessageBox_ServiceUnavailableWarningTitle }); } } else { logger.Error("The service is mandatory but no connection could be established!"); ActionRequired?.Invoke(new MessageEventArgs { Icon = MessageBoxIcon.Error, Message = TextKey.MessageBox_ServiceUnavailableError, Title = TextKey.MessageBox_ServiceUnavailableErrorTitle }); } return success; } private bool TryTerminateConnection() { var serviceEvent = new AutoResetEvent(false); var serviceEventHandler = new CommunicationEventHandler(() => serviceEvent.Set()); runtimeHost.ServiceDisconnected += serviceEventHandler; var success = service.Disconnect(); if (success) { logger.Info("Successfully disconnected from service. Waiting for service to disconnect..."); success = serviceEvent.WaitOne(timeout_ms); if (success) { logger.Info("Service disconnected successfully."); } else { logger.Error($"Service failed to disconnect within {timeout_ms / 1000} seconds!"); } } else { logger.Error("Failed to disconnect from service!"); } runtimeHost.ServiceDisconnected -= serviceEventHandler; return success; } private bool TryStartSession() { var configuration = new ServiceConfiguration { AppConfig = Context.Next.AppConfig, AuthenticationToken = Context.Next.ServiceAuthenticationToken, SessionId = Context.Next.SessionId, Settings = Context.Next.Settings }; var failure = false; var success = false; var serviceEvent = new AutoResetEvent(false); var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); }); var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); }); runtimeHost.ServiceFailed += failureEventHandler; runtimeHost.ServiceSessionStarted += successEventHandler; logger.Info("Starting new service session..."); var communication = service.StartSession(configuration); if (communication.Success) { serviceEvent.WaitOne(timeout_ms); if (success) { logger.Info("Successfully started new service session."); } else if (failure) { logger.Error("An error occurred while attempting to start a new service session! Please check the service log for further information."); } else { logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!"); } } else { logger.Error("Failed to communicate session start to service!"); } runtimeHost.ServiceFailed -= failureEventHandler; runtimeHost.ServiceSessionStarted -= successEventHandler; return success; } private bool TryStopSession() { var failure = false; var success = false; var serviceEvent = new AutoResetEvent(false); var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); }); var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); }); runtimeHost.ServiceFailed += failureEventHandler; runtimeHost.ServiceSessionStopped += successEventHandler; logger.Info("Stopping current service session..."); var communication = service.StopSession(Context.Current.SessionId); if (communication.Success) { serviceEvent.WaitOne(timeout_ms); if (success) { logger.Info("Successfully stopped service session."); } else if (failure) { logger.Error("An error occurred while attempting to stop the current service session! Please check the service log for further information."); } else { logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!"); } } else { logger.Error("Failed to communicate session stop to service!"); } runtimeHost.ServiceFailed -= failureEventHandler; runtimeHost.ServiceSessionStopped -= successEventHandler; return success; } } }