From 6acd40eb74bddc77c313035d0db1947597beee70 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Wed, 3 Oct 2018 14:35:27 +0200 Subject: [PATCH] SEBWIN-220: Extracted user interface dependencies from all IOperations by introducing ActionRequired, ProgressChanged and StatusChanged events. --- SafeExamBrowser.Client/ClientController.cs | 40 ++- .../Operations/BrowserOperation.cs | 8 +- .../ClientHostDisconnectionOperation.cs | 7 +- .../Operations/ClipboardOperation.cs | 7 +- .../Operations/ConfigurationOperation.cs | 9 +- .../Operations/DisplayMonitorOperation.cs | 9 +- .../KeyboardInterceptorOperation.cs | 11 +- .../Operations/MouseInterceptorOperation.cs | 9 +- .../Operations/ProcessMonitorOperation.cs | 11 +- .../Operations/RuntimeConnectionOperation.cs | 11 +- .../Operations/TaskbarOperation.cs | 12 +- .../Operations/WindowMonitorOperation.cs | 9 +- .../ConfigurationRepository.cs | 2 +- .../Events/ActionRequiredEventArgs.cs | 17 ++ .../Events/ActionRequiredEventHandler.cs | 15 ++ .../Events/ProgressChangedEventArgs.cs | 41 +++ .../Events/ProgressChangedEventHandler.cs | 15 ++ .../Events/StatusChangedEventHandler.cs | 17 ++ .../Core/OperationModel/IOperation.cs | 11 +- .../Core/OperationModel/IOperationSequence.cs | 16 +- SafeExamBrowser.Contracts/I18n/TextKey.cs | 1 + .../SafeExamBrowser.Contracts.csproj | 5 + .../UserInterface/IProgressIndicator.cs | 9 +- .../UserInterface/MessageBox/IMessageBox.cs | 5 +- .../OperationModel/OperationSequenceTests.cs | 13 +- .../OperationModel/QueueExtensionTests.cs | 36 +++ .../LazyInitializationOperationTests.cs | 43 +++- .../SafeExamBrowser.Core.UnitTests.csproj | 1 + .../OperationModel/OperationSequence.cs | 30 ++- .../OperationModel/QueueExtensions.cs | 24 ++ .../Operations/CommunicationHostOperation.cs | 11 +- .../Operations/DelegateOperation.cs | 5 +- .../Operations/I18nOperation.cs | 5 +- .../Operations/LazyInitializationOperation.cs | 55 +++- .../Properties/AssemblyInfo.cs | 2 + .../SafeExamBrowser.Core.csproj | 1 + .../Operations/ConfigurationOperationTests.cs | 239 ++++++++---------- .../RuntimeControllerTests.cs | 73 ++++++ .../SafeExamBrowser.Runtime.UnitTests.csproj | 1 - SafeExamBrowser.Runtime/CompositionRoot.cs | 4 +- .../Operations/ClientOperation.cs | 9 +- .../Operations/ClientTerminationOperation.cs | 8 +- .../Operations/ConfigurationOperation.cs | 120 ++------- .../Events/ConfigurationCompletedEventArgs.cs | 17 ++ .../Events/PasswordRequiredEventArgs.cs | 8 +- .../Operations/KioskModeOperation.cs | 13 +- .../Operations/ServiceOperation.cs | 9 +- .../SessionInitializationOperation.cs | 7 +- SafeExamBrowser.Runtime/RuntimeController.cs | 175 ++++++++++++- .../SafeExamBrowser.Runtime.csproj | 2 + .../MessageBox.cs | 18 +- .../RuntimeWindow.xaml.cs | 8 +- .../SplashScreen.xaml.cs | 8 +- .../SplashScreen.xaml.cs | 8 +- 54 files changed, 865 insertions(+), 385 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventArgs.cs create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventHandler.cs create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventArgs.cs create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventHandler.cs create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/Events/StatusChangedEventHandler.cs create mode 100644 SafeExamBrowser.Core.UnitTests/OperationModel/QueueExtensionTests.cs create mode 100644 SafeExamBrowser.Core/OperationModel/QueueExtensions.cs create mode 100644 SafeExamBrowser.Runtime/Operations/Events/ConfigurationCompletedEventArgs.cs rename SafeExamBrowser.Runtime.UnitTests/Operations/PasswordDialogResultStub.cs => SafeExamBrowser.Runtime/Operations/Events/PasswordRequiredEventArgs.cs (57%) diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index 07f47a62..8cffa761 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -7,7 +7,6 @@ */ using System; -using System.ComponentModel; using System.IO; using SafeExamBrowser.Contracts.Browser; using SafeExamBrowser.Contracts.Communication.Data; @@ -18,6 +17,7 @@ using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; @@ -97,7 +97,8 @@ namespace SafeExamBrowser.Client logger.Info("Initiating startup procedure..."); splashScreen = uiFactory.CreateSplashScreen(); - operations.ProgressIndicator = splashScreen; + operations.ProgressChanged += Operations_ProgressChanged; + operations.StatusChanged += Operations_StatusChanged; var success = operations.TryPerform() == OperationResult.Success; @@ -276,6 +277,39 @@ namespace SafeExamBrowser.Client shutdown.Invoke(); } + private void Operations_ProgressChanged(ProgressChangedEventArgs args) + { + if (args.CurrentValue.HasValue) + { + splashScreen?.SetValue(args.CurrentValue.Value); + } + + if (args.IsIndeterminate == true) + { + splashScreen?.SetIndeterminate(); + } + + if (args.MaxValue.HasValue) + { + splashScreen?.SetMaxValue(args.MaxValue.Value); + } + + if (args.Progress == true) + { + splashScreen?.Progress(); + } + + if (args.Regress == true) + { + splashScreen?.Regress(); + } + } + + private void Operations_StatusChanged(TextKey status) + { + splashScreen?.UpdateText(status); + } + private void Runtime_ConnectionLost() { logger.Error("Lost connection to the runtime!"); @@ -285,7 +319,7 @@ namespace SafeExamBrowser.Client shutdown.Invoke(); } - private void Taskbar_QuitButtonClicked(CancelEventArgs args) + private void Taskbar_QuitButtonClicked(System.ComponentModel.CancelEventArgs args) { var result = messageBox.Show(TextKey.MessageBox_Quit, TextKey.MessageBox_QuitTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question); diff --git a/SafeExamBrowser.Client/Operations/BrowserOperation.cs b/SafeExamBrowser.Client/Operations/BrowserOperation.cs index de7b0c62..c0bb2cc6 100644 --- a/SafeExamBrowser.Client/Operations/BrowserOperation.cs +++ b/SafeExamBrowser.Client/Operations/BrowserOperation.cs @@ -9,6 +9,7 @@ using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface; @@ -24,7 +25,8 @@ namespace SafeExamBrowser.Client.Operations private ITaskbar taskbar; private IUserInterfaceFactory uiFactory; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public BrowserOperation( IApplicationController browserController, @@ -43,7 +45,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing browser..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeBrowser, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeBrowser); var browserButton = uiFactory.CreateApplicationButton(browserInfo); @@ -63,7 +65,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Terminating browser..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_TerminateBrowser, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_TerminateBrowser); browserController.Terminate(); } diff --git a/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs b/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs index 64cca6d6..06589f0a 100644 --- a/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs +++ b/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs @@ -10,8 +10,8 @@ using System.Threading; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Client.Operations { @@ -25,7 +25,8 @@ namespace SafeExamBrowser.Client.Operations private ILogger logger; private int timeout_ms; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged { add { } remove { } } public ClientHostDisconnectionOperation(IClientHost clientHost, ILogger logger, int timeout_ms) { @@ -50,6 +51,8 @@ namespace SafeExamBrowser.Client.Operations var disconnectedEvent = new AutoResetEvent(false); var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set()); + // TODO: Update status! + clientHost.RuntimeDisconnected += disconnectedEventHandler; if (clientHost.IsConnected) diff --git a/SafeExamBrowser.Client/Operations/ClipboardOperation.cs b/SafeExamBrowser.Client/Operations/ClipboardOperation.cs index 681caf96..a2b4cebe 100644 --- a/SafeExamBrowser.Client/Operations/ClipboardOperation.cs +++ b/SafeExamBrowser.Client/Operations/ClipboardOperation.cs @@ -7,9 +7,9 @@ */ using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Client.Operations @@ -19,7 +19,8 @@ namespace SafeExamBrowser.Client.Operations private ILogger logger; private INativeMethods nativeMethods; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public ClipboardOperation(ILogger logger, INativeMethods nativeMethods) { @@ -47,7 +48,7 @@ namespace SafeExamBrowser.Client.Operations private void EmptyClipboard() { logger.Info("Emptying clipboard..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_EmptyClipboard); + StatusChanged?.Invoke(TextKey.ProgressIndicator_EmptyClipboard); nativeMethods.EmptyClipboard(); } diff --git a/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs index c78fce78..ee1e6539 100644 --- a/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs @@ -7,12 +7,12 @@ */ using System; -using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Client.Operations { @@ -22,7 +22,8 @@ namespace SafeExamBrowser.Client.Operations private ILogger logger; private IRuntimeProxy runtime; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public ConfigurationOperation(ClientConfiguration configuration, ILogger logger, IRuntimeProxy runtime) { @@ -34,7 +35,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing application configuration..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeConfiguration); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeConfiguration); try { diff --git a/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs b/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs index 8bd8896a..972bd2f9 100644 --- a/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs @@ -7,10 +7,10 @@ */ using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; -using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface.Taskbar; namespace SafeExamBrowser.Client.Operations @@ -21,7 +21,8 @@ namespace SafeExamBrowser.Client.Operations private ILogger logger; private ITaskbar taskbar; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public DisplayMonitorOperation(IDisplayMonitor displayMonitor, ILogger logger, ITaskbar taskbar) { @@ -33,7 +34,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing working area..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeWorkingArea); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeWorkingArea); displayMonitor.PreventSleepMode(); displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight()); @@ -50,7 +51,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Restoring working area..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_RestoreWorkingArea); + StatusChanged?.Invoke(TextKey.ProgressIndicator_RestoreWorkingArea); displayMonitor.StopMonitoringDisplayChanges(); displayMonitor.ResetPrimaryDisplay(); diff --git a/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs b/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs index 444ea1cd..ffd5caa7 100644 --- a/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs +++ b/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs @@ -7,10 +7,10 @@ */ using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; -using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Client.Operations @@ -21,8 +21,9 @@ namespace SafeExamBrowser.Client.Operations private ILogger logger; private INativeMethods nativeMethods; - public IProgressIndicator ProgressIndicator { private get; set; } - + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; + public KeyboardInterceptorOperation( IKeyboardInterceptor keyboardInterceptor, ILogger logger, @@ -36,7 +37,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Starting keyboard interception..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartKeyboardInterception); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StartKeyboardInterception); nativeMethods.RegisterKeyboardHook(keyboardInterceptor); @@ -51,7 +52,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Stopping keyboard interception..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopKeyboardInterception); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopKeyboardInterception); nativeMethods.DeregisterKeyboardHook(keyboardInterceptor); } diff --git a/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs b/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs index d47c173a..1692e304 100644 --- a/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs +++ b/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs @@ -7,10 +7,10 @@ */ using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; -using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Client.Operations @@ -21,7 +21,8 @@ namespace SafeExamBrowser.Client.Operations private IMouseInterceptor mouseInterceptor; private INativeMethods nativeMethods; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public MouseInterceptorOperation( ILogger logger, @@ -36,7 +37,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Starting mouse interception..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartMouseInterception); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StartMouseInterception); nativeMethods.RegisterMouseHook(mouseInterceptor); @@ -51,7 +52,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Stopping mouse interception..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopMouseInterception); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopMouseInterception); nativeMethods.DeregisterMouseHook(mouseInterceptor); } diff --git a/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs b/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs index 92f0f9d1..9d534f83 100644 --- a/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs @@ -6,12 +6,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Core.OperationModel; 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.Monitoring; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Client.Operations { @@ -21,7 +21,8 @@ namespace SafeExamBrowser.Client.Operations private IProcessMonitor processMonitor; private Settings settings; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public ProcessMonitorOperation(ILogger logger, IProcessMonitor processMonitor, Settings settings) { @@ -33,7 +34,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing process monitoring..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeProcessMonitoring); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeProcessMonitoring); if (settings.KioskMode == KioskMode.DisableExplorerShell) { @@ -51,7 +52,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Stopping process monitoring..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopProcessMonitoring); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopProcessMonitoring); if (settings.KioskMode == KioskMode.DisableExplorerShell) { diff --git a/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs b/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs index 5d20ac3a..99adb8a6 100644 --- a/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs +++ b/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs @@ -7,11 +7,11 @@ */ using System; -using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Communication.Proxies; +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Client.Operations { @@ -22,7 +22,8 @@ namespace SafeExamBrowser.Client.Operations private IRuntimeProxy runtime; private Guid token; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public RuntimeConnectionOperation(ILogger logger, IRuntimeProxy runtime, Guid token) { @@ -34,7 +35,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing runtime connection..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeRuntimeConnection); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeRuntimeConnection); connected = runtime.Connect(token); @@ -58,7 +59,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Closing runtime connection..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_CloseRuntimeConnection); + StatusChanged?.Invoke(TextKey.ProgressIndicator_CloseRuntimeConnection); if (connected) { diff --git a/SafeExamBrowser.Client/Operations/TaskbarOperation.cs b/SafeExamBrowser.Client/Operations/TaskbarOperation.cs index e5174df4..a3375ada 100644 --- a/SafeExamBrowser.Client/Operations/TaskbarOperation.cs +++ b/SafeExamBrowser.Client/Operations/TaskbarOperation.cs @@ -6,10 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Core; -using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; +using SafeExamBrowser.Contracts.Core; +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.SystemComponents; @@ -32,7 +33,8 @@ namespace SafeExamBrowser.Client.Operations private IUserInterfaceFactory uiFactory; private IText text; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public TaskbarOperation( ILogger logger, @@ -63,7 +65,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing taskbar..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeTaskbar); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeTaskbar); if (settings.AllowApplicationLog) { @@ -96,7 +98,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Terminating taskbar..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_TerminateTaskbar); + StatusChanged?.Invoke(TextKey.ProgressIndicator_TerminateTaskbar); if (settings.AllowApplicationLog) { diff --git a/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs b/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs index 0cdcc4fa..3308a9d0 100644 --- a/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs @@ -8,10 +8,10 @@ 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.Monitoring; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Client.Operations { @@ -21,7 +21,8 @@ namespace SafeExamBrowser.Client.Operations private ILogger logger; private IWindowMonitor windowMonitor; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public WindowMonitorOperation(KioskMode kioskMode, ILogger logger, IWindowMonitor windowMonitor) { @@ -33,7 +34,7 @@ namespace SafeExamBrowser.Client.Operations public OperationResult Perform() { logger.Info("Initializing window monitoring..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeWindowMonitoring); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeWindowMonitoring); if (kioskMode == KioskMode.DisableExplorerShell) { @@ -56,7 +57,7 @@ namespace SafeExamBrowser.Client.Operations public void Revert() { logger.Info("Stopping window monitoring..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopWindowMonitoring); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopWindowMonitoring); if (kioskMode != KioskMode.None) { diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index 280068db..af18ef18 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -88,7 +88,7 @@ namespace SafeExamBrowser.Configuration CurrentSettings = new Settings(); - CurrentSettings.KioskMode = KioskMode.CreateNewDesktop; + CurrentSettings.KioskMode = KioskMode.None; CurrentSettings.ServicePolicy = ServicePolicy.Optional; CurrentSettings.Browser.StartUrl = "https://www.safeexambrowser.org/testing"; diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventArgs.cs b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventArgs.cs new file mode 100644 index 00000000..729d1c41 --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventArgs.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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/. + */ + +namespace SafeExamBrowser.Contracts.Core.OperationModel.Events +{ + /// + /// Base class for all event arguments used for . + /// + public class ActionRequiredEventArgs + { + } +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventHandler.cs b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventHandler.cs new file mode 100644 index 00000000..e25ab622 --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ActionRequiredEventHandler.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2018 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/. + */ + +namespace SafeExamBrowser.Contracts.Core.OperationModel.Events +{ + /// + /// Event handler used to indicate that an requires user interaction. + /// + public delegate void ActionRequiredEventHandler(ActionRequiredEventArgs args); +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventArgs.cs b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventArgs.cs new file mode 100644 index 00000000..a0f661fa --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventArgs.cs @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 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/. + */ + +namespace SafeExamBrowser.Contracts.Core.OperationModel.Events +{ + /// + /// The event arguments used for . + /// + public class ProgressChangedEventArgs + { + /// + /// Specifies the current progress value, if set. + /// + public int? CurrentValue { get; set; } + + /// + /// Indicates that the progress value is indeterminate, if set. + /// + public bool? IsIndeterminate { get; set; } + + /// + /// Sets the maximum progress value, if set. + /// + public int? MaxValue { get; set; } + + /// + /// Indicates that the current progress value has increased by 1, if set. + /// + public bool? Progress { get; set; } + + /// + /// Indicates that the current progress value has decreased by 1, if set. + /// + public bool? Regress { get; set; } + } +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventHandler.cs b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventHandler.cs new file mode 100644 index 00000000..0832c0c4 --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/Events/ProgressChangedEventHandler.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2018 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/. + */ + +namespace SafeExamBrowser.Contracts.Core.OperationModel.Events +{ + /// + /// Event handler used to indicate that the progress of an has changed. + /// + public delegate void ProgressChangedEventHandler(ProgressChangedEventArgs args); +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/Events/StatusChangedEventHandler.cs b/SafeExamBrowser.Contracts/Core/OperationModel/Events/StatusChangedEventHandler.cs new file mode 100644 index 00000000..7075d994 --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/Events/StatusChangedEventHandler.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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 SafeExamBrowser.Contracts.I18n; + +namespace SafeExamBrowser.Contracts.Core.OperationModel.Events +{ + /// + /// Event handler used to indicate that the status of an has changed. + /// + public delegate void StatusChangedEventHandler(TextKey status); +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs b/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs index f6070470..dad3186c 100644 --- a/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs +++ b/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; namespace SafeExamBrowser.Contracts.Core.OperationModel { @@ -16,9 +16,14 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel public interface IOperation { /// - /// The progress indicator to be used to show status information to the user. Will be ignored if null. + /// Event fired when the operation requires user interaction. /// - IProgressIndicator ProgressIndicator { set; } + event ActionRequiredEventHandler ActionRequired; + + /// + /// Event fired when the status of the operation has changed. + /// + event StatusChangedEventHandler StatusChanged; /// /// Performs the operation. diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs b/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs index 8a450f74..a1202c47 100644 --- a/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs +++ b/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; namespace SafeExamBrowser.Contracts.Core.OperationModel { @@ -24,9 +24,19 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel public interface IOperationSequence { /// - /// The progress indicator to be used when executing an operation. Will be ignored if null. + /// Event fired when an operation requires user interaction. /// - IProgressIndicator ProgressIndicator { set; } + event ActionRequiredEventHandler ActionRequired; + + /// + /// Event fired when the progress of the sequence has changed. + /// + event ProgressChangedEventHandler ProgressChanged; + + /// + /// Event fired when the status of an operation has changed. + /// + event StatusChangedEventHandler StatusChanged; /// /// Tries to perform the operations of this sequence according to their initialized order. If any operation fails, the already diff --git a/SafeExamBrowser.Contracts/I18n/TextKey.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs index dd9dce29..4ea88db8 100644 --- a/SafeExamBrowser.Contracts/I18n/TextKey.cs +++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs @@ -50,6 +50,7 @@ namespace SafeExamBrowser.Contracts.I18n PasswordDialog_Confirm, PasswordDialog_SettingsPasswordRequired, PasswordDialog_SettingsPasswordRequiredTitle, + // TODO: Rename these... ProgressIndicator_CloseRuntimeConnection, ProgressIndicator_EmptyClipboard, ProgressIndicator_FinalizeServiceSession, diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index d776f3c4..2103c185 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -57,6 +57,11 @@ + + + + + diff --git a/SafeExamBrowser.Contracts/UserInterface/IProgressIndicator.cs b/SafeExamBrowser.Contracts/UserInterface/IProgressIndicator.cs index 4408e32d..fb2a0705 100644 --- a/SafeExamBrowser.Contracts/UserInterface/IProgressIndicator.cs +++ b/SafeExamBrowser.Contracts/UserInterface/IProgressIndicator.cs @@ -16,14 +16,14 @@ namespace SafeExamBrowser.Contracts.UserInterface public interface IProgressIndicator { /// - /// Updates the progress value according to the specified amount. + /// Increases the current progress value by 1. /// - void Progress(int amount = 1); + void Progress(); /// - /// Regresses the progress value according to the specified amount. + /// Decreases the current progress value by 1. /// - void Regress(int amount = 1); + void Regress(); /// /// Sets the style of the progress indicator to indeterminate (Progress and Regress won't have any effect when called). @@ -42,6 +42,7 @@ namespace SafeExamBrowser.Contracts.UserInterface /// /// Updates the status text. If the busy flag is set, an animation will be shown to indicate a long-running operation. + /// TODO: Automatically show busy indication in implementations after e.g. 2 seconds! /// void UpdateText(TextKey key, bool showBusyIndication = false); } diff --git a/SafeExamBrowser.Contracts/UserInterface/MessageBox/IMessageBox.cs b/SafeExamBrowser.Contracts/UserInterface/MessageBox/IMessageBox.cs index 6103b19a..bb3d7f9a 100644 --- a/SafeExamBrowser.Contracts/UserInterface/MessageBox/IMessageBox.cs +++ b/SafeExamBrowser.Contracts/UserInterface/MessageBox/IMessageBox.cs @@ -7,6 +7,7 @@ */ using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.UserInterface.Windows; namespace SafeExamBrowser.Contracts.UserInterface.MessageBox { @@ -18,11 +19,11 @@ namespace SafeExamBrowser.Contracts.UserInterface.MessageBox /// /// Shows a message box according to the specified parameters and returns the result chosen by the user. /// - MessageBoxResult Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information); + MessageBoxResult Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information, IWindow parent = null); /// /// Shows a message box according to the specified parameters and returns the result chosen by the user. /// - MessageBoxResult Show(TextKey message, TextKey title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information); + MessageBoxResult Show(TextKey message, TextKey title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information, IWindow parent = null); } } diff --git a/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs b/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs index d078e1c1..ab2cee5a 100644 --- a/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs +++ b/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs @@ -12,7 +12,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Core.OperationModel; namespace SafeExamBrowser.Core.UnitTests.OperationModel @@ -251,10 +250,8 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel public void MustNotFailInCaseOfUnexpectedError() { var sut = new OperationSequence(loggerMock.Object, new Queue()); - var indicatorMock = new Mock(); - indicatorMock.Setup(i => i.SetMaxValue(It.IsAny())).Throws(); - sut.ProgressIndicator = indicatorMock.Object; + sut.ProgressChanged += (args) => throw new Exception(); var result = sut.TryPerform(); @@ -406,10 +403,8 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel public void MustNotFailInCaseOfUnexpectedErrorWhenRepeating() { var sut = new OperationSequence(loggerMock.Object, new Queue()); - var indicatorMock = new Mock(); - indicatorMock.Setup(i => i.SetMaxValue(It.IsAny())).Throws(); - sut.ProgressIndicator = indicatorMock.Object; + sut.ProgressChanged += (args) => throw new Exception(); var result = sut.TryRepeat(); @@ -570,10 +565,8 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel public void MustNotFailInCaseOfUnexpectedErrorWhenReverting() { var sut = new OperationSequence(loggerMock.Object, new Queue()); - var indicatorMock = new Mock(); - indicatorMock.Setup(i => i.SetIndeterminate()).Throws(); - sut.ProgressIndicator = indicatorMock.Object; + sut.ProgressChanged += (args) => throw new Exception(); var success = sut.TryRevert(); diff --git a/SafeExamBrowser.Core.UnitTests/OperationModel/QueueExtensionTests.cs b/SafeExamBrowser.Core.UnitTests/OperationModel/QueueExtensionTests.cs new file mode 100644 index 00000000..67ca5548 --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/OperationModel/QueueExtensionTests.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SafeExamBrowser.Core.OperationModel; + +namespace SafeExamBrowser.Core.UnitTests.OperationModel +{ + [TestClass] + public class QueueExtensionTests + { + [TestMethod] + public void MustCorrectlyIterateThroughQueue() + { + var order = 0; + var queue = new Queue(Enumerable.Range(1, 25)); + var action = new Action(i => + { + Assert.AreEqual(i, ++order); + Assert.AreEqual(queue.ElementAt(i - 1), i); + }); + + queue.ForEach(action); + + Assert.AreEqual(queue.Count, order); + } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs b/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs index 39419e62..18d643e5 100644 --- a/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs @@ -10,7 +10,8 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.Core.OperationModel; -using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; +using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Core.Operations; namespace SafeExamBrowser.Core.UnitTests.Operations @@ -94,23 +95,47 @@ namespace SafeExamBrowser.Core.UnitTests.Operations } [TestMethod] - public void MustUpdateProgressIndicator() + public void MustCorrectlyHandleEventSubscription() { IOperation initialize() { return operationMock.Object; }; - var sut = new LazyInitializationOperation(initialize) - { - ProgressIndicator = new Mock().Object - }; + var actionRequired = 0; + var actionRequiredHandler = new ActionRequiredEventHandler(args => actionRequired++); + var statusChanged = 0; + var statusChangedHandler = new StatusChangedEventHandler(t => statusChanged++); + var sut = new LazyInitializationOperation(initialize); + + sut.ActionRequired += actionRequiredHandler; + sut.StatusChanged += statusChangedHandler; sut.Perform(); - sut.Repeat(); - sut.Revert(); - operationMock.VerifySet(o => o.ProgressIndicator = It.IsAny(), Times.Exactly(3)); + operationMock.Raise(o => o.ActionRequired += null, new ActionRequiredEventArgs()); + operationMock.Raise(o => o.StatusChanged += null, default(TextKey)); + + Assert.AreEqual(1, actionRequired); + Assert.AreEqual(1, statusChanged); + + sut.ActionRequired -= actionRequiredHandler; + sut.StatusChanged -= statusChangedHandler; + + operationMock.Raise(o => o.ActionRequired += null, new ActionRequiredEventArgs()); + operationMock.Raise(o => o.StatusChanged += null, default(TextKey)); + + Assert.AreEqual(1, actionRequired); + Assert.AreEqual(1, statusChanged); + + sut.ActionRequired += actionRequiredHandler; + sut.StatusChanged += statusChangedHandler; + + operationMock.Raise(o => o.ActionRequired += null, new ActionRequiredEventArgs()); + operationMock.Raise(o => o.StatusChanged += null, default(TextKey)); + + Assert.AreEqual(2, actionRequired); + Assert.AreEqual(2, statusChanged); } [TestMethod] diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index 9df2ddfc..d79a245b 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -78,6 +78,7 @@ + diff --git a/SafeExamBrowser.Core/OperationModel/OperationSequence.cs b/SafeExamBrowser.Core/OperationModel/OperationSequence.cs index 6530f72f..c7511d08 100644 --- a/SafeExamBrowser.Core/OperationModel/OperationSequence.cs +++ b/SafeExamBrowser.Core/OperationModel/OperationSequence.cs @@ -10,8 +10,8 @@ using System; using System.Collections.Generic; using System.Linq; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.OperationModel { @@ -24,7 +24,19 @@ namespace SafeExamBrowser.Core.OperationModel private Queue operations = new Queue(); private Stack stack = new Stack(); - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired + { + add { operations.ForEach(o => o.ActionRequired += value); } + remove { operations.ForEach(o => o.ActionRequired -= value); } + } + + public event ProgressChangedEventHandler ProgressChanged; + + public event StatusChangedEventHandler StatusChanged + { + add { operations.ForEach(o => o.StatusChanged += value); } + remove { operations.ForEach(o => o.StatusChanged -= value); } + } public OperationSequence(ILogger logger, Queue operations) { @@ -92,12 +104,11 @@ namespace SafeExamBrowser.Core.OperationModel { if (indeterminate) { - ProgressIndicator?.SetIndeterminate(); + ProgressChanged?.Invoke(new ProgressChangedEventArgs { IsIndeterminate = true }); } else { - ProgressIndicator?.SetValue(0); - ProgressIndicator?.SetMaxValue(operations.Count); + ProgressChanged?.Invoke(new ProgressChangedEventArgs { CurrentValue = 0, MaxValue = operations.Count }); } } @@ -111,7 +122,6 @@ namespace SafeExamBrowser.Core.OperationModel try { - operation.ProgressIndicator = ProgressIndicator; result = operation.Perform(); } catch (Exception e) @@ -124,7 +134,7 @@ namespace SafeExamBrowser.Core.OperationModel return result; } - ProgressIndicator?.Progress(); + ProgressChanged?.Invoke(new ProgressChangedEventArgs { Progress = true }); } return OperationResult.Success; @@ -138,7 +148,6 @@ namespace SafeExamBrowser.Core.OperationModel try { - operation.ProgressIndicator = ProgressIndicator; result = operation.Repeat(); } catch (Exception e) @@ -151,7 +160,7 @@ namespace SafeExamBrowser.Core.OperationModel return result; } - ProgressIndicator?.Progress(); + ProgressChanged?.Invoke(new ProgressChangedEventArgs { Progress = true }); } return OperationResult.Success; @@ -167,7 +176,6 @@ namespace SafeExamBrowser.Core.OperationModel try { - operation.ProgressIndicator = ProgressIndicator; operation.Revert(); } catch (Exception e) @@ -178,7 +186,7 @@ namespace SafeExamBrowser.Core.OperationModel if (regress) { - ProgressIndicator?.Regress(); + ProgressChanged?.Invoke(new ProgressChangedEventArgs { Regress = true }); } } diff --git a/SafeExamBrowser.Core/OperationModel/QueueExtensions.cs b/SafeExamBrowser.Core/OperationModel/QueueExtensions.cs new file mode 100644 index 00000000..f94c2bf4 --- /dev/null +++ b/SafeExamBrowser.Core/OperationModel/QueueExtensions.cs @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018 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; +using System.Collections.Generic; + +namespace SafeExamBrowser.Core.OperationModel +{ + internal static class QueueExtensions + { + internal static void ForEach(this Queue queue, Action action) + { + foreach (var element in queue) + { + action(element); + } + } + } +} diff --git a/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs b/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs index 1b15466e..81f49400 100644 --- a/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs +++ b/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs @@ -8,9 +8,9 @@ using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Operations { @@ -23,7 +23,8 @@ namespace SafeExamBrowser.Core.Operations private ICommunicationHost host; private ILogger logger; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public CommunicationHostOperation(ICommunicationHost host, ILogger logger) { @@ -34,7 +35,7 @@ namespace SafeExamBrowser.Core.Operations public OperationResult Perform() { logger.Info("Starting communication host..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartCommunicationHost); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StartCommunicationHost); host.Start(); @@ -46,7 +47,7 @@ namespace SafeExamBrowser.Core.Operations if (!host.IsRunning) { logger.Info("Restarting communication host..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_RestartCommunicationHost); + StatusChanged?.Invoke(TextKey.ProgressIndicator_RestartCommunicationHost); host.Stop(); host.Start(); @@ -58,7 +59,7 @@ namespace SafeExamBrowser.Core.Operations public void Revert() { logger.Info("Stopping communication host..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopCommunicationHost); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopCommunicationHost); host.Stop(); } diff --git a/SafeExamBrowser.Core/Operations/DelegateOperation.cs b/SafeExamBrowser.Core/Operations/DelegateOperation.cs index d1193dee..b77ff039 100644 --- a/SafeExamBrowser.Core/Operations/DelegateOperation.cs +++ b/SafeExamBrowser.Core/Operations/DelegateOperation.cs @@ -8,7 +8,7 @@ using System; using SafeExamBrowser.Contracts.Core.OperationModel; -using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; namespace SafeExamBrowser.Core.Operations { @@ -22,7 +22,8 @@ namespace SafeExamBrowser.Core.Operations private Action repeat; private Action revert; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged { add { } remove { } } public DelegateOperation(Action perform, Action repeat = null, Action revert = null) { diff --git a/SafeExamBrowser.Core/Operations/I18nOperation.cs b/SafeExamBrowser.Core/Operations/I18nOperation.cs index 25454d10..544f3d1a 100644 --- a/SafeExamBrowser.Core/Operations/I18nOperation.cs +++ b/SafeExamBrowser.Core/Operations/I18nOperation.cs @@ -8,9 +8,9 @@ using System.Globalization; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Operations { @@ -23,7 +23,8 @@ namespace SafeExamBrowser.Core.Operations private IText text; private ITextResource textResource; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged { add { } remove { } } public I18nOperation(ILogger logger, IText text, ITextResource textResource) { diff --git a/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs b/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs index 1c3a791f..949b11b2 100644 --- a/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs +++ b/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs @@ -8,7 +8,7 @@ using System; using SafeExamBrowser.Contracts.Core.OperationModel; -using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; namespace SafeExamBrowser.Core.Operations { @@ -22,7 +22,52 @@ namespace SafeExamBrowser.Core.Operations private Func initialize; private IOperation operation; - public IProgressIndicator ProgressIndicator { get; set; } + private event ActionRequiredEventHandler ActionRequiredImpl; + private event StatusChangedEventHandler StatusChangedImpl; + + public event ActionRequiredEventHandler ActionRequired + { + add + { + ActionRequiredImpl += value; + + if (operation != null) + { + operation.ActionRequired += value; + } + } + remove + { + ActionRequiredImpl -= value; + + if (operation != null) + { + operation.ActionRequired -= value; + } + } + } + + public event StatusChangedEventHandler StatusChanged + { + add + { + StatusChangedImpl += value; + + if (operation != null) + { + operation.StatusChanged += value; + } + } + remove + { + StatusChangedImpl -= value; + + if (operation != null) + { + operation.StatusChanged -= value; + } + } + } public LazyInitializationOperation(Func initialize) { @@ -32,21 +77,19 @@ namespace SafeExamBrowser.Core.Operations public OperationResult Perform() { operation = initialize.Invoke(); - operation.ProgressIndicator = ProgressIndicator; + operation.ActionRequired += ActionRequiredImpl; + operation.StatusChanged += StatusChangedImpl; return operation.Perform(); } public OperationResult Repeat() { - operation.ProgressIndicator = ProgressIndicator; - return operation.Repeat(); } public void Revert() { - operation.ProgressIndicator = ProgressIndicator; operation.Revert(); } } diff --git a/SafeExamBrowser.Core/Properties/AssemblyInfo.cs b/SafeExamBrowser.Core/Properties/AssemblyInfo.cs index 399a8176..8a2e7eed 100644 --- a/SafeExamBrowser.Core/Properties/AssemblyInfo.cs +++ b/SafeExamBrowser.Core/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -14,6 +15,7 @@ using System.Runtime.InteropServices; // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("SafeExamBrowser.Core.UnitTests")] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("3d6fdbb6-a4af-4626-bb2b-bf329d44f9cc")] diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index d5f36572..d5e2dcc1 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -54,6 +54,7 @@ + diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs index ca599c05..20e45362 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs @@ -11,18 +11,13 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.Communication.Data; -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.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; -using SafeExamBrowser.Contracts.UserInterface.MessageBox; -using SafeExamBrowser.Contracts.UserInterface.Windows; using SafeExamBrowser.Runtime.Operations; +using SafeExamBrowser.Runtime.Operations.Events; namespace SafeExamBrowser.Runtime.UnitTests.Operations { @@ -31,14 +26,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { private AppConfig appConfig; private Mock logger; - private Mock messageBox; - private Mock passwordDialog; private Mock repository; private Mock resourceLoader; - private Mock runtimeHost; private Settings settings; - private Mock text; - private Mock uiFactory; private ConfigurationOperation sut; [TestInitialize] @@ -46,22 +36,15 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { appConfig = new AppConfig(); logger = new Mock(); - messageBox = new Mock(); - passwordDialog = new Mock(); repository = new Mock(); resourceLoader = new Mock(); - runtimeHost = new Mock(); settings = new Settings(); - text = new Mock(); - uiFactory = new Mock(); appConfig.AppDataFolder = @"C:\Not\Really\AppData"; appConfig.DefaultSettingsFileName = "SettingsDummy.txt"; appConfig.ProgramDataFolder = @"C:\Not\Really\ProgramData"; repository.SetupGet(r => r.CurrentSettings).Returns(settings); - - uiFactory.Setup(f => f.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(passwordDialog.Object); } [TestMethod] @@ -75,7 +58,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); sut.Perform(); var resource = new Uri(url); @@ -93,7 +76,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); sut.Perform(); var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); @@ -110,7 +93,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); sut.Perform(); var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); @@ -121,7 +104,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustFallbackToDefaultsAsLastPrio() { - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); sut.Perform(); repository.Verify(r => r.LoadDefaultSettings(), Times.Once); @@ -131,10 +114,16 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations public void MustAbortIfWishedByUser() { appConfig.ProgramDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); - messageBox.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.Yes); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); + sut.ActionRequired += args => + { + if (args is ConfigurationCompletedEventArgs c) + { + c.AbortStartup = true; + } + }; var result = sut.Perform(); @@ -144,10 +133,16 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustNotAbortIfNotWishedByUser() { - messageBox.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.No); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); + sut.ActionRequired += args => + { + if (args is ConfigurationCompletedEventArgs c) + { + c.AbortStartup = false; + } + }; var result = sut.Perform(); @@ -160,10 +155,16 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations settings.ConfigurationMode = ConfigurationMode.Exam; repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); - sut.Perform(); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); + sut.ActionRequired += args => + { + if (args is ConfigurationCompletedEventArgs c) + { + Assert.Fail(); + } + }; - messageBox.Verify(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + sut.Perform(); } [TestMethod] @@ -171,10 +172,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { repository.Setup(r => r.LoadDefaultSettings()); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); sut.Perform(); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new string[] { }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new string[] { }); sut.Perform(); repository.Verify(r => r.LoadDefaultSettings(), Times.Exactly(2)); @@ -185,20 +186,26 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { var uri = @"an/invalid\uri.'*%yolo/()你好"; - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", uri }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", uri }); sut.Perform(); } [TestMethod] public void MustOnlyAllowToEnterAdminPasswordFiveTimes() { - var result = new PasswordDialogResultStub { Success = true }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - passwordDialog.Setup(d => d.Show(null)).Returns(result); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.AdminPasswordNeeded); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Success = true; + } + }; + sut.Perform(); repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Exactly(5)); @@ -207,13 +214,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustOnlyAllowToEnterSettingsPasswordFiveTimes() { - var result = new PasswordDialogResultStub { Success = true }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - passwordDialog.Setup(d => d.Show(null)).Returns(result); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Success = true; + } + }; + sut.Perform(); repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Exactly(5)); @@ -223,14 +236,21 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations public void MustSucceedIfAdminPasswordCorrect() { var password = "test"; - var result = new PasswordDialogResultStub { Password = password, Success = true }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - passwordDialog.Setup(d => d.Show(null)).Returns(result); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.AdminPasswordNeeded); repository.Setup(r => r.LoadSettings(It.IsAny(), password, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Password = password; + p.Success = true; + } + }; + sut.Perform(); repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Once); @@ -241,14 +261,21 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations public void MustSucceedIfSettingsPasswordCorrect() { var password = "test"; - var result = new PasswordDialogResultStub { Password = password, Success = true }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - passwordDialog.Setup(d => d.Show(null)).Returns(result); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); repository.Setup(r => r.LoadSettings(It.IsAny(), null, password)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Password = password; + p.Success = true; + } + }; + sut.Perform(); repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Once); @@ -258,13 +285,18 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustAbortAskingForAdminPasswordIfDecidedByUser() { - var dialogResult = new PasswordDialogResultStub { Success = false }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - passwordDialog.Setup(d => d.Show(null)).Returns(dialogResult); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.AdminPasswordNeeded); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Success = false; + } + }; var result = sut.Perform(); @@ -274,13 +306,18 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustAbortAskingForSettingsPasswordIfDecidedByUser() { - var dialogResult = new PasswordDialogResultStub { Success = false }; var url = @"http://www.safeexambrowser.org/whatever.seb"; - passwordDialog.Setup(d => d.Show(null)).Returns(dialogResult); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Success = false; + } + }; var result = sut.Perform(); @@ -291,18 +328,23 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations public void MustAllowEnteringBothPasswords() { var adminPassword = "xyz"; - var adminResult = new PasswordDialogResultStub { Password = adminPassword, Success = true }; - var adminCallback = new Action(() => passwordDialog.Setup(d => d.Show(null)).Returns(adminResult)); var settingsPassword = "abc"; - var settingsResult = new PasswordDialogResultStub { Password = settingsPassword, Success = true }; - var settingsCallback = new Action(() => passwordDialog.Setup(d => d.Show(null)).Returns(settingsResult)); var url = @"http://www.safeexambrowser.org/whatever.seb"; - repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded).Callback(settingsCallback); - repository.Setup(r => r.LoadSettings(It.IsAny(), null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded).Callback(adminCallback); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded); repository.Setup(r => r.LoadSettings(It.IsAny(), adminPassword, settingsPassword)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + sut.ActionRequired += args => + { + if (args is PasswordRequiredEventArgs p) + { + p.Password = p.Purpose == PasswordRequestPurpose.Administrator ? adminPassword : settingsPassword; + p.Success = true; + } + }; + sut.Perform(); repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Once); @@ -310,79 +352,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations repository.Verify(r => r.LoadSettings(It.IsAny(), adminPassword, settingsPassword), Times.Once); } - [TestMethod] - public void MustRequestPasswordViaDialogOnDefaultDesktop() - { - var clientProxy = new Mock(); - var session = new Mock(); - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true }); - repository.SetupGet(r => r.CurrentSession).Returns(session.Object); - repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); - session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); - settings.KioskMode = KioskMode.DisableExplorerShell; - - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); - sut.Perform(); - - clientProxy.Verify(c => c.RequestPassword(It.IsAny(), It.IsAny()), Times.Never); - passwordDialog.Verify(p => p.Show(null), Times.AtLeastOnce); - session.VerifyGet(s => s.ClientProxy, Times.Never); - } - - [TestMethod] - public void MustRequestPasswordViaClientDuringReconfigurationOnNewDesktop() - { - var clientProxy = new Mock(); - var communication = new CommunicationResult(true); - var passwordReceived = new Action((p, id) => - { - runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true }); - }); - var session = new Mock(); - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - clientProxy.Setup(c => c.RequestPassword(It.IsAny(), It.IsAny())).Returns(communication).Callback(passwordReceived); - passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true }); - repository.SetupGet(r => r.CurrentSession).Returns(session.Object); - repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); - session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); - settings.KioskMode = KioskMode.CreateNewDesktop; - - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); - sut.Perform(); - - clientProxy.Verify(c => c.RequestPassword(It.IsAny(), It.IsAny()), Times.AtLeastOnce); - passwordDialog.Verify(p => p.Show(null), Times.Never); - session.VerifyGet(s => s.ClientProxy, Times.AtLeastOnce); - } - - [TestMethod] - public void MustAbortAskingForPasswordViaClientIfDecidedByUser() - { - var clientProxy = new Mock(); - var communication = new CommunicationResult(true); - var passwordReceived = new Action((p, id) => - { - runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false }); - }); - var session = new Mock(); - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - clientProxy.Setup(c => c.RequestPassword(It.IsAny(), It.IsAny())).Returns(communication).Callback(passwordReceived); - repository.SetupGet(r => r.CurrentSession).Returns(session.Object); - repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); - session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); - settings.KioskMode = KioskMode.CreateNewDesktop; - - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); - - var result = sut.Perform(); - - Assert.AreEqual(OperationResult.Aborted, result); - } - [TestMethod] public void MustNotWaitForPasswordViaClientIfCommunicationHasFailed() { @@ -397,7 +366,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); settings.KioskMode = KioskMode.CreateNewDesktop; - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); var result = sut.Perform(); @@ -412,7 +381,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations resourceLoader.Setup(r => r.IsHtmlResource(It.IsAny())).Returns(false); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.InvalidData); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); var result = sut.Perform(); @@ -427,7 +396,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations resourceLoader.Setup(r => r.IsHtmlResource(It.IsAny())).Returns(true); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.InvalidData); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); var result = sut.Perform(); @@ -444,7 +413,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations repository.SetupGet(r => r.ReconfigurationFilePath).Returns(resource.AbsolutePath); repository.Setup(r => r.LoadSettings(It.Is(u => u.Equals(resource)), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); var result = sut.Repeat(); @@ -461,7 +430,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations repository.SetupGet(r => r.ReconfigurationFilePath).Returns(null as string); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, null); + sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, null); var result = sut.Repeat(); diff --git a/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs b/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs index 4e7e7830..447736d0 100644 --- a/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs @@ -18,5 +18,78 @@ namespace SafeExamBrowser.Runtime.UnitTests { Assert.Fail(); } + + //[TestMethod] + //public void MustRequestPasswordViaDialogOnDefaultDesktop() + //{ + // var clientProxy = new Mock(); + // var session = new Mock(); + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true }); + // repository.SetupGet(r => r.CurrentSession).Returns(session.Object); + // repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); + // session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); + // settings.KioskMode = KioskMode.DisableExplorerShell; + + // sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + // sut.Perform(); + + // clientProxy.Verify(c => c.RequestPassword(It.IsAny(), It.IsAny()), Times.Never); + // passwordDialog.Verify(p => p.Show(null), Times.AtLeastOnce); + // session.VerifyGet(s => s.ClientProxy, Times.Never); + //} + + //[TestMethod] + //public void MustRequestPasswordViaClientDuringReconfigurationOnNewDesktop() + //{ + // var clientProxy = new Mock(); + // var communication = new CommunicationResult(true); + // var passwordReceived = new Action((p, id) => + // { + // runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true }); + // }); + // var session = new Mock(); + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // clientProxy.Setup(c => c.RequestPassword(It.IsAny(), It.IsAny())).Returns(communication).Callback(passwordReceived); + // passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true }); + // repository.SetupGet(r => r.CurrentSession).Returns(session.Object); + // repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); + // session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); + // settings.KioskMode = KioskMode.CreateNewDesktop; + + // sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + // sut.Perform(); + + // clientProxy.Verify(c => c.RequestPassword(It.IsAny(), It.IsAny()), Times.AtLeastOnce); + // passwordDialog.Verify(p => p.Show(null), Times.Never); + // session.VerifyGet(s => s.ClientProxy, Times.AtLeastOnce); + //} + + //[TestMethod] + //public void MustAbortAskingForPasswordViaClientIfDecidedByUser() + //{ + // var clientProxy = new Mock(); + // var communication = new CommunicationResult(true); + // var passwordReceived = new Action((p, id) => + // { + // runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false }); + // }); + // var session = new Mock(); + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // clientProxy.Setup(c => c.RequestPassword(It.IsAny(), It.IsAny())).Returns(communication).Callback(passwordReceived); + // repository.SetupGet(r => r.CurrentSession).Returns(session.Object); + // repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); + // session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); + // settings.KioskMode = KioskMode.CreateNewDesktop; + + // sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, resourceLoader.Object, new[] { "blubb.exe", url }); + + // var result = sut.Perform(); + + // Assert.AreEqual(OperationResult.Aborted, result); + //} } } diff --git a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj index bf1f637b..3115bdf3 100644 --- a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj +++ b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj @@ -81,7 +81,6 @@ - diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 46bdb00b..d86b3179 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -71,7 +71,7 @@ namespace SafeExamBrowser.Runtime bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource)); bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger)); - sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, messageBox, resourceLoader, runtimeHost, text, uiFactory, args)); + sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, resourceLoader, args)); sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost)); sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy)); sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS)); @@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); var sessionSequence = new OperationSequence(logger, sessionOperations); - RuntimeController = new RuntimeController(appConfig, configuration, logger, messageBox, bootstrapSequence, sessionSequence, runtimeHost, serviceProxy, shutdown, uiFactory); + RuntimeController = new RuntimeController(appConfig, configuration, logger, messageBox, bootstrapSequence, sessionSequence, runtimeHost, serviceProxy, shutdown, text, uiFactory); } internal void LogStartupInformation() diff --git a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs index b7ad6154..ad1fa6e6 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs @@ -12,9 +12,9 @@ using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi.Events; @@ -30,7 +30,8 @@ namespace SafeExamBrowser.Runtime.Operations protected IProxyFactory proxyFactory; protected IRuntimeHost runtimeHost; - public IProgressIndicator ProgressIndicator { protected get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; protected IProcess ClientProcess { @@ -62,7 +63,7 @@ namespace SafeExamBrowser.Runtime.Operations public virtual OperationResult Perform() { - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartClient, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StartClient); var success = TryStartClient(); @@ -87,7 +88,7 @@ namespace SafeExamBrowser.Runtime.Operations { if (ClientProcess != null && !ClientProcess.HasTerminated) { - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopClient, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopClient); TryStopClient(); } } diff --git a/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs index 88ac8830..126045e5 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs @@ -6,10 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.WindowsApi; @@ -18,6 +19,9 @@ namespace SafeExamBrowser.Runtime.Operations { internal class ClientTerminationOperation : ClientOperation { + public new event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public new event StatusChangedEventHandler StatusChanged; + public ClientTerminationOperation( IConfigurationRepository configuration, ILogger logger, @@ -39,7 +43,7 @@ namespace SafeExamBrowser.Runtime.Operations if (ClientProcess != null && !ClientProcess.HasTerminated) { - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopClient, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_StopClient); success = TryStopClient(); } diff --git a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs index a1781a75..52b25fce 100644 --- a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs @@ -8,17 +8,14 @@ using System; using System.IO; -using System.Threading; -using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Communication.Data; -using SafeExamBrowser.Contracts.Communication.Events; -using SafeExamBrowser.Contracts.Communication.Hosts; 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; -using SafeExamBrowser.Contracts.UserInterface.MessageBox; +using SafeExamBrowser.Runtime.Operations.Events; namespace SafeExamBrowser.Runtime.Operations { @@ -26,42 +23,31 @@ namespace SafeExamBrowser.Runtime.Operations { private IConfigurationRepository configuration; private ILogger logger; - private IMessageBox messageBox; private IResourceLoader resourceLoader; - private IRuntimeHost runtimeHost; private AppConfig appConfig; - private IText text; - private IUserInterfaceFactory uiFactory; private string[] commandLineArgs; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired; + public event StatusChangedEventHandler StatusChanged; public ConfigurationOperation( AppConfig appConfig, IConfigurationRepository configuration, ILogger logger, - IMessageBox messageBox, IResourceLoader resourceLoader, - IRuntimeHost runtimeHost, - IText text, - IUserInterfaceFactory uiFactory, string[] commandLineArgs) { this.appConfig = appConfig; this.logger = logger; - this.messageBox = messageBox; this.configuration = configuration; this.resourceLoader = resourceLoader; - this.runtimeHost = runtimeHost; - this.text = text; - this.uiFactory = uiFactory; this.commandLineArgs = commandLineArgs; } public OperationResult Perform() { logger.Info("Initializing application configuration..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeConfiguration); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeConfiguration); var isValidUri = TryInitializeSettingsUri(out Uri uri); @@ -86,7 +72,7 @@ namespace SafeExamBrowser.Runtime.Operations public OperationResult Repeat() { logger.Info("Initializing new application configuration..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeConfiguration); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeConfiguration); var isValidUri = TryValidateSettingsUri(configuration.ReconfigurationFilePath, out Uri uri); @@ -123,18 +109,17 @@ namespace SafeExamBrowser.Runtime.Operations if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded) { - var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings; - var aborted = !TryGetPassword(purpose, out string password); + var result = TryGetPassword(status); - adminAttempts += purpose == PasswordRequestPurpose.Administrator ? 1 : 0; - adminPassword = purpose == PasswordRequestPurpose.Administrator ? password : adminPassword; - settingsAttempts += purpose == PasswordRequestPurpose.Settings ? 1 : 0; - settingsPassword = purpose == PasswordRequestPurpose.Settings ? password : settingsPassword; - - if (aborted) + if (!result.Success) { return OperationResult.Aborted; } + + adminAttempts += status == LoadStatus.AdminPasswordNeeded ? 1 : 0; + adminPassword = status == LoadStatus.AdminPasswordNeeded ? result.Password : adminPassword; + settingsAttempts += status == LoadStatus.SettingsPasswordNeeded ? 1 : 0; + settingsPassword = status == LoadStatus.SettingsPasswordNeeded ? result.Password : settingsPassword; } else { @@ -150,64 +135,14 @@ namespace SafeExamBrowser.Runtime.Operations return status == LoadStatus.Success ? OperationResult.Success : OperationResult.Failed; } - private bool TryGetPassword(PasswordRequestPurpose purpose, out string password) + private PasswordRequiredEventArgs TryGetPassword(LoadStatus status) { - var isStartup = configuration.CurrentSession == null; - var isRunningOnDefaultDesktop = configuration.CurrentSettings?.KioskMode == KioskMode.DisableExplorerShell; + var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings; + var args = new PasswordRequiredEventArgs { Purpose = purpose }; - if (isStartup || isRunningOnDefaultDesktop) - { - return TryGetPasswordViaDialog(purpose, out password); - } - else - { - return TryGetPasswordViaClient(purpose, out password); - } - } + ActionRequired?.Invoke(args); - private bool TryGetPasswordViaDialog(PasswordRequestPurpose purpose, out string password) - { - var isAdmin = purpose == PasswordRequestPurpose.Administrator; - var message = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequired : TextKey.PasswordDialog_SettingsPasswordRequired; - var title = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequiredTitle : TextKey.PasswordDialog_SettingsPasswordRequiredTitle; - var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title)); - var result = dialog.Show(); - var success = result.Success; - - password = success ? result.Password : default(string); - - return success; - } - - private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password) - { - var requestId = Guid.NewGuid(); - var response = default(PasswordReplyEventArgs); - var responseEvent = new AutoResetEvent(false); - var responseEventHandler = new CommunicationEventHandler((args) => - { - if (args.RequestId == requestId) - { - response = args; - responseEvent.Set(); - } - }); - - runtimeHost.PasswordReceived += responseEventHandler; - - var communication = configuration.CurrentSession.ClientProxy.RequestPassword(purpose, requestId); - - if (communication.Success) - { - responseEvent.WaitOne(); - } - - var success = response?.Success == true; - - runtimeHost.PasswordReceived -= responseEventHandler; - password = success ? response.Password : default(string); - - return success; + return args; } private void HandleInvalidData(ref LoadStatus status, Uri uri) @@ -273,26 +208,19 @@ namespace SafeExamBrowser.Runtime.Operations { if (result == OperationResult.Success && configuration.CurrentSettings.ConfigurationMode == ConfigurationMode.ConfigureClient) { - var abort = IsConfigurationSufficient(); + var args = new ConfigurationCompletedEventArgs(); - logger.Info($"The user chose to {(abort ? "abort" : "continue")} after successful client configuration."); + ActionRequired?.Invoke(args); - if (abort) + logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration."); + + if (args.AbortStartup) { result = OperationResult.Aborted; } } } - private bool IsConfigurationSufficient() - { - var message = text.Get(TextKey.MessageBox_ClientConfigurationQuestion); - var title = text.Get(TextKey.MessageBox_ClientConfigurationQuestionTitle); - var abort = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question); - - return abort == MessageBoxResult.Yes; - } - private void LogOperationResult(OperationResult result) { switch (result) diff --git a/SafeExamBrowser.Runtime/Operations/Events/ConfigurationCompletedEventArgs.cs b/SafeExamBrowser.Runtime/Operations/Events/ConfigurationCompletedEventArgs.cs new file mode 100644 index 00000000..ab91b08b --- /dev/null +++ b/SafeExamBrowser.Runtime/Operations/Events/ConfigurationCompletedEventArgs.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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 SafeExamBrowser.Contracts.Core.OperationModel.Events; + +namespace SafeExamBrowser.Runtime.Operations.Events +{ + internal class ConfigurationCompletedEventArgs : ActionRequiredEventArgs + { + public bool AbortStartup { get; set; } + } +} diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/PasswordDialogResultStub.cs b/SafeExamBrowser.Runtime/Operations/Events/PasswordRequiredEventArgs.cs similarity index 57% rename from SafeExamBrowser.Runtime.UnitTests/Operations/PasswordDialogResultStub.cs rename to SafeExamBrowser.Runtime/Operations/Events/PasswordRequiredEventArgs.cs index 8fc620a9..bf50c951 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/PasswordDialogResultStub.cs +++ b/SafeExamBrowser.Runtime/Operations/Events/PasswordRequiredEventArgs.cs @@ -6,13 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.UserInterface.Windows; +using SafeExamBrowser.Contracts.Communication.Data; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; -namespace SafeExamBrowser.Runtime.UnitTests.Operations +namespace SafeExamBrowser.Runtime.Operations.Events { - internal class PasswordDialogResultStub : IPasswordDialogResult + internal class PasswordRequiredEventArgs : ActionRequiredEventArgs { public string Password { get; set; } + public PasswordRequestPurpose Purpose { get; set; } public bool Success { get; set; } } } diff --git a/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs index a75a36bf..159aaa77 100644 --- a/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs @@ -9,9 +9,9 @@ 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; using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Runtime.Operations @@ -27,7 +27,8 @@ namespace SafeExamBrowser.Runtime.Operations private IDesktop newDesktop; private IDesktop originalDesktop; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public KioskModeOperation( IConfigurationRepository configuration, @@ -48,7 +49,7 @@ namespace SafeExamBrowser.Runtime.Operations kioskMode = configuration.CurrentSettings.KioskMode; logger.Info($"Initializing kiosk mode '{kioskMode}'..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeKioskMode); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeKioskMode); switch (kioskMode) { @@ -86,7 +87,7 @@ namespace SafeExamBrowser.Runtime.Operations public void Revert() { logger.Info($"Reverting kiosk mode '{kioskMode}'..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_RevertKioskMode); + StatusChanged?.Invoke(TextKey.ProgressIndicator_RevertKioskMode); switch (kioskMode) { @@ -142,13 +143,13 @@ namespace SafeExamBrowser.Runtime.Operations private void TerminateExplorerShell() { - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerTermination, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_WaitExplorerTermination); explorerShell.Terminate(); } private void RestartExplorerShell() { - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_WaitExplorerStartup, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_WaitExplorerStartup); explorerShell.Start(); } } diff --git a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs index 574ed748..70918297 100644 --- a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs @@ -10,9 +10,9 @@ 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; namespace SafeExamBrowser.Runtime.Operations { @@ -23,7 +23,8 @@ namespace SafeExamBrowser.Runtime.Operations private ILogger logger; private IServiceProxy service; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public ServiceOperation(IConfigurationRepository configuration, ILogger logger, IServiceProxy service) { @@ -35,7 +36,7 @@ namespace SafeExamBrowser.Runtime.Operations public OperationResult Perform() { logger.Info($"Initializing service session..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeServiceSession); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeServiceSession); mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory; connected = service.Connect(); @@ -73,7 +74,7 @@ namespace SafeExamBrowser.Runtime.Operations public void Revert() { logger.Info("Finalizing service session..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_FinalizeServiceSession); + StatusChanged?.Invoke(TextKey.ProgressIndicator_FinalizeServiceSession); if (connected) { diff --git a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs index c7525399..aaecfd9b 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs @@ -9,9 +9,9 @@ using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Runtime.Operations { @@ -21,7 +21,8 @@ namespace SafeExamBrowser.Runtime.Operations private ILogger logger; private IRuntimeHost runtimeHost; - public IProgressIndicator ProgressIndicator { private get; set; } + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged; public SessionInitializationOperation(IConfigurationRepository configuration, ILogger logger, IRuntimeHost runtimeHost) { @@ -52,7 +53,7 @@ namespace SafeExamBrowser.Runtime.Operations private void InitializeSessionConfiguration() { logger.Info("Initializing new session configuration..."); - ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeSession, true); + StatusChanged?.Invoke(TextKey.ProgressIndicator_InitializeSession); configuration.InitializeSessionConfiguration(); runtimeHost.StartupToken = configuration.CurrentSession.StartupToken; diff --git a/SafeExamBrowser.Runtime/RuntimeController.cs b/SafeExamBrowser.Runtime/RuntimeController.cs index 6a63cdfb..c1ee5415 100644 --- a/SafeExamBrowser.Runtime/RuntimeController.cs +++ b/SafeExamBrowser.Runtime/RuntimeController.cs @@ -7,6 +7,8 @@ */ using System; +using System.Threading; +using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; @@ -14,11 +16,13 @@ using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface.MessageBox; using SafeExamBrowser.Contracts.UserInterface.Windows; +using SafeExamBrowser.Runtime.Operations.Events; namespace SafeExamBrowser.Runtime { @@ -37,6 +41,7 @@ namespace SafeExamBrowser.Runtime private IServiceProxy service; private ISplashScreen splashScreen; private Action shutdown; + private IText text; private IUserInterfaceFactory uiFactory; public RuntimeController( @@ -49,6 +54,7 @@ namespace SafeExamBrowser.Runtime IRuntimeHost runtimeHost, IServiceProxy service, Action shutdown, + IText text, IUserInterfaceFactory uiFactory) { this.appConfig = appConfig; @@ -60,6 +66,7 @@ namespace SafeExamBrowser.Runtime this.sessionSequence = sessionSequence; this.service = service; this.shutdown = shutdown; + this.text = text; this.uiFactory = uiFactory; } @@ -70,8 +77,11 @@ namespace SafeExamBrowser.Runtime runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig); splashScreen = uiFactory.CreateSplashScreen(appConfig); - bootstrapSequence.ProgressIndicator = splashScreen; - sessionSequence.ProgressIndicator = runtimeWindow; + bootstrapSequence.ProgressChanged += BootstrapSequence_ProgressChanged; + bootstrapSequence.StatusChanged += BootstrapSequence_StatusChanged; + sessionSequence.ActionRequired += SessionSequence_ActionRequired; + sessionSequence.ProgressChanged += SessionSequence_ProgressChanged; + sessionSequence.StatusChanged += SessionSequence_StatusChanged; splashScreen.Show(); @@ -93,7 +103,7 @@ namespace SafeExamBrowser.Runtime logger.Info("Application startup aborted!"); logger.Log(string.Empty); - messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error); + messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen); } return initialized && sessionRunning; @@ -128,7 +138,7 @@ namespace SafeExamBrowser.Runtime logger.Info("Shutdown procedure failed!"); logger.Log(string.Empty); - messageBox.Show(TextKey.MessageBox_ShutdownError, TextKey.MessageBox_ShutdownErrorTitle, icon: MessageBoxIcon.Error); + messageBox.Show(TextKey.MessageBox_ShutdownError, TextKey.MessageBox_ShutdownErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen); } splashScreen?.Close(); @@ -171,7 +181,7 @@ namespace SafeExamBrowser.Runtime if (result == OperationResult.Failed) { // TODO: Check if message box is rendered on new desktop as well! -> E.g. if settings for reconfiguration are invalid - messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error); + messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow); if (!initial) { @@ -201,7 +211,7 @@ namespace SafeExamBrowser.Runtime else { logger.Info("### --- Session Stop Failed --- ###"); - messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error); + messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow); } } @@ -229,10 +239,44 @@ namespace SafeExamBrowser.Runtime configuration.CurrentSession.ClientProxy.ConnectionLost -= Client_ConnectionLost; } + private void BootstrapSequence_ProgressChanged(ProgressChangedEventArgs args) + { + // TODO: Duplicated code (for splashScreen as well as runtimeWindow)! + if (args.CurrentValue.HasValue) + { + splashScreen?.SetValue(args.CurrentValue.Value); + } + + if (args.IsIndeterminate == true) + { + splashScreen?.SetIndeterminate(); + } + + if (args.MaxValue.HasValue) + { + splashScreen?.SetMaxValue(args.MaxValue.Value); + } + + if (args.Progress == true) + { + splashScreen?.Progress(); + } + + if (args.Regress == true) + { + splashScreen?.Regress(); + } + } + + private void BootstrapSequence_StatusChanged(TextKey status) + { + splashScreen?.UpdateText(status); + } + private void ClientProcess_Terminated(int exitCode) { logger.Error($"Client application has unexpectedly terminated with exit code {exitCode}!"); - // TODO: Check if message box is rendered on new desktop as well -> otherwise shutdown is blocked! + // TODO: Check if message box is rendered on new desktop as well -> otherwise shutdown is blocked! Check if parent needed! messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error); shutdown.Invoke(); @@ -241,7 +285,7 @@ namespace SafeExamBrowser.Runtime private void Client_ConnectionLost() { logger.Error("Lost connection to the client application!"); - // TODO: Check if message box is rendered on new desktop as well -> otherwise shutdown is blocked! + // TODO: Check if message box is rendered on new desktop as well -> otherwise shutdown is blocked! Check if parent needed! messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error); shutdown.Invoke(); @@ -270,5 +314,120 @@ namespace SafeExamBrowser.Runtime logger.Info("Received shutdown request from the client application."); shutdown.Invoke(); } + + private void SessionSequence_ActionRequired(ActionRequiredEventArgs args) + { + switch (args) + { + case ConfigurationCompletedEventArgs a: + AskIfConfigurationSufficient(a); + break; + case PasswordRequiredEventArgs p: + AskForPassword(p); + break; + } + } + + private void AskIfConfigurationSufficient(ConfigurationCompletedEventArgs args) + { + var message = TextKey.MessageBox_ClientConfigurationQuestion; + var title = TextKey.MessageBox_ClientConfigurationQuestionTitle; + var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, runtimeWindow); + + args.AbortStartup = result == MessageBoxResult.Yes; + } + + private void AskForPassword(PasswordRequiredEventArgs args) + { + var isStartup = configuration.CurrentSession == null; + var isRunningOnDefaultDesktop = configuration.CurrentSettings?.KioskMode == KioskMode.DisableExplorerShell; + + if (isStartup || isRunningOnDefaultDesktop) + { + TryGetPasswordViaDialog(args); + } + else + { + TryGetPasswordViaClient(args); + } + } + + private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args) + { + var isAdmin = args.Purpose == PasswordRequestPurpose.Administrator; + var message = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequired : TextKey.PasswordDialog_SettingsPasswordRequired; + var title = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequiredTitle : TextKey.PasswordDialog_SettingsPasswordRequiredTitle; + var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title)); + var result = dialog.Show(runtimeWindow); + + args.Password = result.Password; + args.Success = result.Success; + } + + private void TryGetPasswordViaClient(PasswordRequiredEventArgs args) + { + var requestId = Guid.NewGuid(); + var response = default(PasswordReplyEventArgs); + var responseEvent = new AutoResetEvent(false); + var responseEventHandler = new CommunicationEventHandler((a) => + { + if (a.RequestId == requestId) + { + response = a; + responseEvent.Set(); + } + }); + + runtimeHost.PasswordReceived += responseEventHandler; + + var communication = configuration.CurrentSession.ClientProxy.RequestPassword(args.Purpose, requestId); + + if (communication.Success) + { + responseEvent.WaitOne(); + args.Password = response.Password; + args.Success = response.Success; + } + else + { + args.Password = default(string); + args.Success = false; + } + + runtimeHost.PasswordReceived -= responseEventHandler; + } + + private void SessionSequence_ProgressChanged(ProgressChangedEventArgs args) + { + if (args.CurrentValue.HasValue) + { + runtimeWindow?.SetValue(args.CurrentValue.Value); + } + + if (args.IsIndeterminate == true) + { + runtimeWindow?.SetIndeterminate(); + } + + if (args.MaxValue.HasValue) + { + runtimeWindow?.SetMaxValue(args.MaxValue.Value); + } + + if (args.Progress == true) + { + runtimeWindow?.Progress(); + } + + if (args.Regress == true) + { + runtimeWindow?.Regress(); + } + } + + private void SessionSequence_StatusChanged(TextKey status) + { + runtimeWindow?.UpdateText(status); + } } } diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index 18e52cc9..158bf775 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -89,6 +89,8 @@ + + diff --git a/SafeExamBrowser.UserInterface.Classic/MessageBox.cs b/SafeExamBrowser.UserInterface.Classic/MessageBox.cs index cea59c5c..1993bb81 100644 --- a/SafeExamBrowser.UserInterface.Classic/MessageBox.cs +++ b/SafeExamBrowser.UserInterface.Classic/MessageBox.cs @@ -9,6 +9,7 @@ using System.Windows; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.UserInterface.MessageBox; +using SafeExamBrowser.Contracts.UserInterface.Windows; using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBox.MessageBoxResult; namespace SafeExamBrowser.UserInterface.Classic @@ -22,16 +23,25 @@ namespace SafeExamBrowser.UserInterface.Classic this.text = text; } - public MessageBoxResult Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information) + public MessageBoxResult Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information, IWindow parent = null) { - var result = System.Windows.MessageBox.Show(message, title, ToButton(action), ToImage(icon)); + var result = default(System.Windows.MessageBoxResult); + + if (parent is Window window) + { + result = window.Dispatcher.Invoke(() => System.Windows.MessageBox.Show(window, message, title, ToButton(action), ToImage(icon))); + } + else + { + result = System.Windows.MessageBox.Show(message, title, ToButton(action), ToImage(icon)); + } return ToResult(result); } - public MessageBoxResult Show(TextKey message, TextKey title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information) + public MessageBoxResult Show(TextKey message, TextKey title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information, IWindow parent = null) { - return Show(text.Get(message), text.Get(title), action, icon); + return Show(text.Get(message), text.Get(title), action, icon, parent); } private MessageBoxButton ToButton(MessageBoxAction action) diff --git a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs index decad95d..c46f22a5 100644 --- a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs @@ -81,14 +81,14 @@ namespace SafeExamBrowser.UserInterface.Classic }); } - public void Progress(int amount = 1) + public void Progress() { - model.CurrentProgress += amount; + model.CurrentProgress += 1; } - public void Regress(int amount = 1) + public void Regress() { - model.CurrentProgress -= amount; + model.CurrentProgress -= 1; } public void SetIndeterminate() diff --git a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs index 7e841f7c..8682ce84 100644 --- a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs @@ -77,14 +77,14 @@ namespace SafeExamBrowser.UserInterface.Classic Dispatcher.Invoke(base.Show); } - public void Progress(int amount = 1) + public void Progress() { - model.CurrentProgress += amount; + model.CurrentProgress += 1; } - public void Regress(int amount = 1) + public void Regress() { - model.CurrentProgress -= amount; + model.CurrentProgress -= 1; } public void SetIndeterminate() diff --git a/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs index 5c29e4c5..d3a48787 100644 --- a/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs @@ -75,14 +75,14 @@ namespace SafeExamBrowser.UserInterface.Windows10 Dispatcher.Invoke(base.Show); } - public void Progress(int amount = 1) + public void Progress() { - model.CurrentProgress += amount; + model.CurrentProgress += 1; } - public void Regress(int amount = 1) + public void Regress() { - model.CurrentProgress -= amount; + model.CurrentProgress -= 1; } public void SetIndeterminate()