SEBWIN-220: Extracted user interface dependencies from all IOperations by introducing ActionRequired, ProgressChanged and StatusChanged events.

This commit is contained in:
dbuechel 2018-10-03 14:35:27 +02:00
parent 0b76770f0f
commit 6acd40eb74
54 changed files with 865 additions and 385 deletions

View file

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

View file

@ -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();
}

View file

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

View file

@ -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();
}

View file

@ -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
{

View file

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

View file

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

View file

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

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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";

View file

@ -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
{
/// <summary>
/// Base class for all event arguments used for <see cref="IOperationSequence.ProgressChanged"/>.
/// </summary>
public class ActionRequiredEventArgs
{
}
}

View file

@ -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
{
/// <summary>
/// Event handler used to indicate that an <see cref="IOperation"/> requires user interaction.
/// </summary>
public delegate void ActionRequiredEventHandler(ActionRequiredEventArgs args);
}

View file

@ -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
{
/// <summary>
/// The event arguments used for <see cref="IOperationSequence.ProgressChanged"/>.
/// </summary>
public class ProgressChangedEventArgs
{
/// <summary>
/// Specifies the current progress value, if set.
/// </summary>
public int? CurrentValue { get; set; }
/// <summary>
/// Indicates that the progress value is indeterminate, if set.
/// </summary>
public bool? IsIndeterminate { get; set; }
/// <summary>
/// Sets the maximum progress value, if set.
/// </summary>
public int? MaxValue { get; set; }
/// <summary>
/// Indicates that the current progress value has increased by 1, if set.
/// </summary>
public bool? Progress { get; set; }
/// <summary>
/// Indicates that the current progress value has decreased by 1, if set.
/// </summary>
public bool? Regress { get; set; }
}
}

View file

@ -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
{
/// <summary>
/// Event handler used to indicate that the progress of an <see cref="IOperationSequence"/> has changed.
/// </summary>
public delegate void ProgressChangedEventHandler(ProgressChangedEventArgs args);
}

View file

@ -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
{
/// <summary>
/// Event handler used to indicate that the status of an <see cref="IOperation"/> has changed.
/// </summary>
public delegate void StatusChangedEventHandler(TextKey status);
}

View file

@ -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
{
/// <summary>
/// The progress indicator to be used to show status information to the user. Will be ignored if <c>null</c>.
/// Event fired when the operation requires user interaction.
/// </summary>
IProgressIndicator ProgressIndicator { set; }
event ActionRequiredEventHandler ActionRequired;
/// <summary>
/// Event fired when the status of the operation has changed.
/// </summary>
event StatusChangedEventHandler StatusChanged;
/// <summary>
/// Performs the operation.

View file

@ -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
{
/// <summary>
/// The progress indicator to be used when executing an operation. Will be ignored if <c>null</c>.
/// Event fired when an operation requires user interaction.
/// </summary>
IProgressIndicator ProgressIndicator { set; }
event ActionRequiredEventHandler ActionRequired;
/// <summary>
/// Event fired when the progress of the sequence has changed.
/// </summary>
event ProgressChangedEventHandler ProgressChanged;
/// <summary>
/// Event fired when the status of an operation has changed.
/// </summary>
event StatusChangedEventHandler StatusChanged;
/// <summary>
/// Tries to perform the operations of this sequence according to their initialized order. If any operation fails, the already

View file

@ -50,6 +50,7 @@ namespace SafeExamBrowser.Contracts.I18n
PasswordDialog_Confirm,
PasswordDialog_SettingsPasswordRequired,
PasswordDialog_SettingsPasswordRequiredTitle,
// TODO: Rename these...
ProgressIndicator_CloseRuntimeConnection,
ProgressIndicator_EmptyClipboard,
ProgressIndicator_FinalizeServiceSession,

View file

@ -57,6 +57,11 @@
<Compile Include="Core\IApplicationController.cs" />
<Compile Include="Core\InstanceIdentifier.cs" />
<Compile Include="Core\IRuntimeController.cs" />
<Compile Include="Core\OperationModel\Events\ActionRequiredEventArgs.cs" />
<Compile Include="Core\OperationModel\Events\ActionRequiredEventHandler.cs" />
<Compile Include="Core\OperationModel\Events\ProgressChangedEventArgs.cs" />
<Compile Include="Core\OperationModel\Events\ProgressChangedEventHandler.cs" />
<Compile Include="Core\OperationModel\Events\StatusChangedEventHandler.cs" />
<Compile Include="Core\OperationModel\IOperationSequence.cs" />
<Compile Include="Core\OperationModel\OperationResult.cs" />
<Compile Include="Browser\DownloadEventArgs.cs" />

View file

@ -16,14 +16,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
public interface IProgressIndicator
{
/// <summary>
/// Updates the progress value according to the specified amount.
/// Increases the current progress value by 1.
/// </summary>
void Progress(int amount = 1);
void Progress();
/// <summary>
/// Regresses the progress value according to the specified amount.
/// Decreases the current progress value by 1.
/// </summary>
void Regress(int amount = 1);
void Regress();
/// <summary>
/// Sets the style of the progress indicator to indeterminate (<c>Progress</c> and <c>Regress</c> won't have any effect when called).
@ -42,6 +42,7 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// <summary>
/// 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!
/// </summary>
void UpdateText(TextKey key, bool showBusyIndication = false);
}

View file

@ -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
/// <summary>
/// Shows a message box according to the specified parameters and returns the result chosen by the user.
/// </summary>
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);
/// <summary>
/// Shows a message box according to the specified parameters and returns the result chosen by the user.
/// </summary>
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);
}
}

View file

@ -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<IOperation>());
var indicatorMock = new Mock<IProgressIndicator>();
indicatorMock.Setup(i => i.SetMaxValue(It.IsAny<int>())).Throws<Exception>();
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<IOperation>());
var indicatorMock = new Mock<IProgressIndicator>();
indicatorMock.Setup(i => i.SetMaxValue(It.IsAny<int>())).Throws<Exception>();
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<IOperation>());
var indicatorMock = new Mock<IProgressIndicator>();
indicatorMock.Setup(i => i.SetIndeterminate()).Throws<Exception>();
sut.ProgressIndicator = indicatorMock.Object;
sut.ProgressChanged += (args) => throw new Exception();
var success = sut.TryRevert();

View file

@ -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<int>(Enumerable.Range(1, 25));
var action = new Action<int>(i =>
{
Assert.AreEqual(i, ++order);
Assert.AreEqual(queue.ElementAt(i - 1), i);
});
queue.ForEach(action);
Assert.AreEqual(queue.Count, order);
}
}
}

View file

@ -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<IProgressIndicator>().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<IProgressIndicator>(), 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]

View file

@ -78,6 +78,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="OperationModel\QueueExtensionTests.cs" />
<Compile Include="Operations\CommunicationHostOperationTests.cs" />
<Compile Include="Operations\LazyInitializationOperationTests.cs" />
<Compile Include="Operations\I18nOperationTests.cs" />

View file

@ -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<IOperation> operations = new Queue<IOperation>();
private Stack<IOperation> stack = new Stack<IOperation>();
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<IOperation> 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 });
}
}

View file

@ -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<T>(this Queue<T> queue, Action<T> action)
{
foreach (var element in queue)
{
action(element);
}
}
}
}

View file

@ -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();
}

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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<IOperation> 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<IOperation> 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();
}
}

View file

@ -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")]

View file

@ -54,6 +54,7 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="OperationModel\QueueExtensions.cs" />
<Compile Include="Operations\CommunicationHostOperation.cs" />
<Compile Include="Operations\LazyInitializationOperation.cs" />
<Compile Include="Operations\I18nOperation.cs" />

View file

@ -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<ILogger> logger;
private Mock<IMessageBox> messageBox;
private Mock<IPasswordDialog> passwordDialog;
private Mock<IConfigurationRepository> repository;
private Mock<IResourceLoader> resourceLoader;
private Mock<IRuntimeHost> runtimeHost;
private Settings settings;
private Mock<IText> text;
private Mock<IUserInterfaceFactory> uiFactory;
private ConfigurationOperation sut;
[TestInitialize]
@ -46,22 +36,15 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
appConfig = new AppConfig();
logger = new Mock<ILogger>();
messageBox = new Mock<IMessageBox>();
passwordDialog = new Mock<IPasswordDialog>();
repository = new Mock<IConfigurationRepository>();
resourceLoader = new Mock<IResourceLoader>();
runtimeHost = new Mock<IRuntimeHost>();
settings = new Settings();
text = new Mock<IText>();
uiFactory = new Mock<IUserInterfaceFactory>();
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<string>(), It.IsAny<string>())).Returns(passwordDialog.Object);
}
[TestMethod]
@ -75,7 +58,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), 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<Uri>(), 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<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.Yes);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.No);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), 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<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>()), 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<Uri>(), 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<Uri>(), 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<Uri>(), 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<Uri>(), 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<Uri>(), null, null)).Returns(LoadStatus.AdminPasswordNeeded);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), 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<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), 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<Uri>(), 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<Uri>(), 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<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded).Callback(settingsCallback);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded).Callback(adminCallback);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), null, null), Times.Once);
@ -310,79 +352,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
repository.Verify(r => r.LoadSettings(It.IsAny<Uri>(), adminPassword, settingsPassword), Times.Once);
}
[TestMethod]
public void MustRequestPasswordViaDialogOnDefaultDesktop()
{
var clientProxy = new Mock<IClientProxy>();
var session = new Mock<ISessionData>();
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<Uri>(), 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<PasswordRequestPurpose>(), It.IsAny<Guid>()), 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<IClientProxy>();
var communication = new CommunicationResult(true);
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{
runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true });
});
var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb";
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).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<Uri>(), 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<PasswordRequestPurpose>(), It.IsAny<Guid>()), 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<IClientProxy>();
var communication = new CommunicationResult(true);
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{
runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false });
});
var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb";
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Returns(communication).Callback(passwordReceived);
repository.SetupGet(r => r.CurrentSession).Returns(session.Object);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>())).Returns(false);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>())).Returns(true);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(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<Uri>(), 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();

View file

@ -18,5 +18,78 @@ namespace SafeExamBrowser.Runtime.UnitTests
{
Assert.Fail();
}
//[TestMethod]
//public void MustRequestPasswordViaDialogOnDefaultDesktop()
//{
// var clientProxy = new Mock<IClientProxy>();
// var session = new Mock<ISessionData>();
// 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<Uri>(), 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<PasswordRequestPurpose>(), It.IsAny<Guid>()), 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<IClientProxy>();
// var communication = new CommunicationResult(true);
// var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
// {
// runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true });
// });
// var session = new Mock<ISessionData>();
// var url = @"http://www.safeexambrowser.org/whatever.seb";
// clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).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<Uri>(), 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<PasswordRequestPurpose>(), It.IsAny<Guid>()), 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<IClientProxy>();
// var communication = new CommunicationResult(true);
// var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
// {
// runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false });
// });
// var session = new Mock<ISessionData>();
// var url = @"http://www.safeexambrowser.org/whatever.seb";
// clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Returns(communication).Callback(passwordReceived);
// repository.SetupGet(r => r.CurrentSession).Returns(session.Object);
// repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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);
//}
}
}

View file

@ -81,7 +81,6 @@
<ItemGroup>
<Compile Include="Operations\ConfigurationOperationTests.cs" />
<Compile Include="Operations\KioskModeOperationTests.cs" />
<Compile Include="Operations\PasswordDialogResultStub.cs" />
<Compile Include="Operations\ServiceOperationTests.cs" />
<Compile Include="Operations\ClientOperationTests.cs" />
<Compile Include="Operations\ClientTerminationOperationTests.cs" />

View file

@ -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()

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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<PasswordReplyEventArgs>((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)

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -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();
}
}

View file

@ -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)
{

View file

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

View file

@ -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<PasswordReplyEventArgs>((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);
}
}
}

View file

@ -89,6 +89,8 @@
<Compile Include="Operations\ClientOperation.cs" />
<Compile Include="Operations\ClientTerminationOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.cs" />
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
<Compile Include="Operations\KioskModeOperation.cs" />
<Compile Include="Operations\ServiceOperation.cs" />
<Compile Include="Operations\SessionInitializationOperation.cs" />

View file

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

View file

@ -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()

View file

@ -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()

View file

@ -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()