SEBWIN-219: Finished basic version of configuration operation.

This commit is contained in:
dbuechel 2018-01-19 14:04:12 +01:00
parent 80be746860
commit f321496815
20 changed files with 229 additions and 65 deletions

View file

@ -23,6 +23,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ITaskbar taskbar;
private IUserInterfaceFactory uiFactory;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public BrowserOperation(

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ILogger logger;
private IClientController controller;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public ClientControllerOperation(IClientController controller, ILogger logger)

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ILogger logger;
private INativeMethods nativeMethods;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public ClipboardOperation(ILogger logger, INativeMethods nativeMethods)

View file

@ -21,6 +21,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ILogger logger;
private ITaskbar taskbar;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public DisplayMonitorOperation(IDisplayMonitor displayMonitor, ILogger logger, ITaskbar taskbar)

View file

@ -21,6 +21,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ILogger logger;
private INativeMethods nativeMethods;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public KeyboardInterceptorOperation(

View file

@ -21,6 +21,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private IMouseInterceptor mouseInterceptor;
private INativeMethods nativeMethods;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public MouseInterceptorOperation(

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ILogger logger;
private IProcessMonitor processMonitor;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public ProcessMonitorOperation(ILogger logger, IProcessMonitor processMonitor)

View file

@ -31,6 +31,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private IUserInterfaceFactory uiFactory;
private IText text;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public TaskbarOperation(

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
private ILogger logger;
private IWindowMonitor windowMonitor;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public WindowMonitorOperation(ILogger logger, IWindowMonitor windowMonitor)

View file

@ -12,6 +12,11 @@ namespace SafeExamBrowser.Contracts.Behaviour
{
public interface IOperation
{
/// <summary>
/// Determines whether the startup procedure to which this operation belongs should be aborted.
/// </summary>
bool AbortStartup { get; }
/// <summary>
/// The splash screen to be used to show status information to the user.
/// </summary>

View file

@ -57,8 +57,11 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
sut.FinalizeApplication(operations);
operationA.Verify(o => o.Revert(), Times.Once);
operationA.Verify(o => o.Perform(), Times.Never);
operationB.Verify(o => o.Revert(), Times.Once);
operationB.Verify(o => o.Perform(), Times.Never);
operationC.Verify(o => o.Revert(), Times.Once);
operationC.Verify(o => o.Perform(), Times.Never);
}
[TestMethod]
@ -108,6 +111,27 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
operationC.Verify(o => o.Revert(), Times.Once);
}
[TestMethod]
public void MustNotEvaluateAbortFlag()
{
var operationA = new Mock<IOperation>();
var operationB = new Mock<IOperation>();
var operationC = new Mock<IOperation>();
var operations = new Queue<IOperation>();
operationB.SetupGet(o => o.AbortStartup).Returns(true);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
operations.Enqueue(operationC.Object);
sut.FinalizeApplication(operations);
operationA.Verify(o => o.Revert(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Once);
}
[TestMethod]
public void MustNotFailWithEmptyQueue()
{

View file

@ -12,7 +12,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
@ -45,6 +44,32 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
sut = new StartupController(loggerMock.Object, runtimeInfoMock.Object, systemInfoMock.Object, textMock.Object, uiFactoryMock.Object);
}
[TestMethod]
public void MustCorrectlyAbortProcess()
{
var operationA = new Mock<IOperation>();
var operationB = new Mock<IOperation>();
var operationC = new Mock<IOperation>();
var operations = new Queue<IOperation>();
operationB.SetupGet(o => o.AbortStartup).Returns(true);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
operations.Enqueue(operationC.Object);
var result = sut.TryInitializeApplication(operations);
operationA.Verify(o => o.Perform(), Times.Once);
operationA.Verify(o => o.Revert(), Times.Once);
operationB.Verify(o => o.Perform(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Once);
operationC.Verify(o => o.Perform(), Times.Never);
operationC.Verify(o => o.Revert(), Times.Never);
Assert.IsFalse(result);
}
[TestMethod]
public void MustPerformOperations()
{
@ -60,8 +85,11 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
var result = sut.TryInitializeApplication(operations);
operationA.Verify(o => o.Perform(), Times.Once);
operationA.Verify(o => o.Revert(), Times.Never);
operationB.Verify(o => o.Perform(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Never);
operationC.Verify(o => o.Perform(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Never);
Assert.IsTrue(result);
}
@ -183,5 +211,13 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
Assert.IsTrue(result);
}
[TestMethod]
public void MustNotFailInCaseOfUnexpectedError()
{
uiFactoryMock.Setup(l => l.CreateSplashScreen(It.IsAny<IRuntimeInfo>(), It.IsAny<IText>())).Throws(new Exception());
sut.TryInitializeApplication(new Queue<IOperation>());
}
}
}

View file

@ -22,6 +22,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
private ILogger logger;
private IText text;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public I18nOperation(ILogger logger, IText text)

View file

@ -10,7 +10,6 @@ using System;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
@ -88,8 +87,11 @@ namespace SafeExamBrowser.Core.Behaviour
{
logger.Info("--- Application successfully finalized! ---");
}
else
{
logger.Info("--- Shutdown procedure failed! ---");
}
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
splashScreen?.InvokeClose();
}
}

View file

@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
@ -40,35 +39,56 @@ namespace SafeExamBrowser.Core.Behaviour
public bool TryInitializeApplication(Queue<IOperation> operations)
{
var success = false;
try
{
Initialize(operations.Count);
Perform(operations);
Finish();
success = Perform(operations);
return true;
if (!success)
{
RevertOperations();
}
Finish(success);
}
catch (Exception e)
{
LogAndShowException(e);
RevertOperations();
Finish(false);
return false;
}
return success;
}
private void Perform(Queue<IOperation> operations)
private bool Perform(Queue<IOperation> operations)
{
foreach (var operation in operations)
{
stack.Push(operation);
operation.SplashScreen = splashScreen;
operation.Perform();
try
{
operation.Perform();
}
catch (Exception e)
{
LogAndShowException(e);
return false;
}
if (operation.AbortStartup)
{
return false;
}
splashScreen.Progress();
}
return true;
}
private void RevertOperations()
@ -92,17 +112,6 @@ namespace SafeExamBrowser.Core.Behaviour
private void Initialize(int operationCount)
{
var titleLine = $"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}{Environment.NewLine}";
var copyrightLine = $"/* {runtimeInfo.ProgramCopyright}{Environment.NewLine}";
var emptyLine = $"/* {Environment.NewLine}";
var githubLine = $"/* Please visit https://github.com/SafeExamBrowser for more information.";
logger.Log($"{titleLine}{copyrightLine}{emptyLine}{githubLine}");
logger.Log(string.Empty);
logger.Log($"# Application started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log($"# Running on {systemInfo.OperatingSystemInfo}");
logger.Log(string.Empty);
logger.Info("--- Initiating startup procedure ---");
splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text);
@ -124,12 +133,13 @@ namespace SafeExamBrowser.Core.Behaviour
{
logger.Info("--- Application successfully initialized! ---");
logger.Log(string.Empty);
splashScreen?.InvokeClose();
}
else
{
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Info("--- Startup procedure aborted! ---");
}
splashScreen?.InvokeClose();
}
}
}

View file

@ -157,5 +157,38 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
controller.VerifySet(c => c.Settings = It.IsAny<ISettings>(), Times.Once);
repository.Verify(r => r.LoadDefaults(), Times.Once);
}
[TestMethod]
public void MustAbortIfWishedByUser()
{
var location = Path.GetDirectoryName(GetType().Assembly.Location);
info.SetupGet(r => r.ProgramDataFolder).Returns(location);
uiFactory.Setup(u => u.Show(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.Yes);
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null)
{
SplashScreen = splashScreen.Object
};
sut.Perform();
Assert.IsTrue(sut.AbortStartup);
}
[TestMethod]
public void MustNotAbortIfNotWishedByUser()
{
uiFactory.Setup(u => u.Show(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.No);
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null)
{
SplashScreen = splashScreen.Object
};
sut.Perform();
Assert.IsFalse(sut.AbortStartup);
}
}
}

View file

@ -60,6 +60,7 @@ namespace SafeExamBrowser.Runtime
base.OnStartup(e);
instances.BuildObjectGraph();
LogStartupInformation();
var success = instances.StartupController.TryInitializeApplication(instances.StartupOperations);
@ -76,6 +77,29 @@ namespace SafeExamBrowser.Runtime
}
}
protected override void OnExit(ExitEventArgs e)
{
instances.Logger?.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
base.OnExit(e);
}
private void LogStartupInformation()
{
var runtimeInfo = instances.RuntimeInfo;
var logger = instances.Logger;
var titleLine = $"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}{Environment.NewLine}";
var copyrightLine = $"/* {runtimeInfo.ProgramCopyright}{Environment.NewLine}";
var emptyLine = $"/* {Environment.NewLine}";
var githubLine = $"/* Please visit https://github.com/SafeExamBrowser for more information.";
logger.Log($"{titleLine}{copyrightLine}{emptyLine}{githubLine}");
logger.Log(string.Empty);
logger.Log($"# Application started at {runtimeInfo.ApplicationStartTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log($"# Running on {instances.SystemInfo.OperatingSystemInfo}");
logger.Log(string.Empty);
}
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
var operations = new Queue<IOperation>(instances.StartupOperations.Reverse());

View file

@ -28,6 +28,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private IUserInterfaceFactory uiFactory;
private string[] commandLineArgs;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public ConfigurationOperation(
@ -60,6 +61,13 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
logger.Info($"Loading configuration from '{uri.AbsolutePath}'...");
settings = repository.Load(uri);
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && Abort())
{
AbortStartup = true;
return;
}
}
else
{
@ -67,18 +75,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
settings = repository.LoadDefaults();
}
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
var message = text.Get(TextKey.MessageBox_ConfigureClientSuccess);
var title = text.Get(TextKey.MessageBox_ConfigureClientSuccessTitle);
var quitDialogResult = uiFactory.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question);
if (quitDialogResult == MessageBoxResult.Yes)
{
// TODO: Callback to terminate WPF application
}
}
controller.Settings = settings;
}
@ -119,5 +115,24 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
return isValidUri;
}
private bool Abort()
{
var message = text.Get(TextKey.MessageBox_ConfigureClientSuccess);
var title = text.Get(TextKey.MessageBox_ConfigureClientSuccessTitle);
var quitDialogResult = uiFactory.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question);
var abort = quitDialogResult == MessageBoxResult.Yes;
if (abort)
{
logger.Info("The user chose to terminate the application after successful client configuration.");
}
else
{
logger.Info("The user chose to continue starting up the application after successful client configuration.");
}
return abort;
}
}
}

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private ILogger logger;
private IRuntimeController controller;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public RuntimeControllerOperation(IRuntimeController controller, ILogger logger)

View file

@ -28,37 +28,41 @@ namespace SafeExamBrowser.Runtime
{
internal class CompositionRoot
{
internal ILogger Logger { get; private set; }
internal RuntimeInfo RuntimeInfo { get; private set; }
internal IShutdownController ShutdownController { get; private set; }
internal IStartupController StartupController { get; private set; }
internal ISystemInfo SystemInfo { get; private set; }
internal Queue<IOperation> StartupOperations { get; private set; }
internal void BuildObjectGraph()
{
var args = Environment.GetCommandLineArgs();
var logger = new Logger();
var nativeMethods = new NativeMethods();
var runtimeInfo = new RuntimeInfo();
var settingsRepository = new SettingsRepository();
var systemInfo = new SystemInfo();
var uiFactory = new UserInterfaceFactory();
Initialize(runtimeInfo);
Initialize(logger, runtimeInfo);
Logger = new Logger();
RuntimeInfo = new RuntimeInfo();
SystemInfo = new SystemInfo();
var text = new Text(logger);
var runtimeController = new RuntimeController(new ModuleLogger(logger, typeof(RuntimeController)));
InitializeRuntimeInfo();
InitializeLogging();
ShutdownController = new ShutdownController(logger, runtimeInfo, text, uiFactory);
StartupController = new StartupController(logger, runtimeInfo, systemInfo, text, uiFactory);
var text = new Text(Logger);
var runtimeController = new RuntimeController(new ModuleLogger(Logger, typeof(RuntimeController)));
ShutdownController = new ShutdownController(Logger, RuntimeInfo, text, uiFactory);
StartupController = new StartupController(Logger, RuntimeInfo, SystemInfo, text, uiFactory);
StartupOperations = new Queue<IOperation>();
StartupOperations.Enqueue(new I18nOperation(logger, text));
StartupOperations.Enqueue(new ConfigurationOperation(logger, runtimeController, runtimeInfo, settingsRepository, text, uiFactory, args));
StartupOperations.Enqueue(new I18nOperation(Logger, text));
StartupOperations.Enqueue(new ConfigurationOperation(Logger, runtimeController, RuntimeInfo, settingsRepository, text, uiFactory, args));
//StartupOperations.Enqueue(new KioskModeOperation());
StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, logger));
StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, Logger));
}
private void Initialize(RuntimeInfo runtimeInfo)
private void InitializeRuntimeInfo()
{
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
var executable = Assembly.GetEntryAssembly();
@ -66,25 +70,25 @@ namespace SafeExamBrowser.Runtime
var logFolder = Path.Combine(appDataFolder, "Logs");
var logFilePrefix = startTime.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
runtimeInfo.ApplicationStartTime = startTime;
runtimeInfo.AppDataFolder = appDataFolder;
runtimeInfo.BrowserCachePath = Path.Combine(appDataFolder, "Cache");
runtimeInfo.BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.txt");
runtimeInfo.ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.txt");
runtimeInfo.DefaultSettingsFileName = "SebClientSettings.seb";
runtimeInfo.ProgramCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;
runtimeInfo.ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser));
runtimeInfo.ProgramTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
runtimeInfo.ProgramVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
runtimeInfo.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.txt");
RuntimeInfo.ApplicationStartTime = startTime;
RuntimeInfo.AppDataFolder = appDataFolder;
RuntimeInfo.BrowserCachePath = Path.Combine(appDataFolder, "Cache");
RuntimeInfo.BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.txt");
RuntimeInfo.ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.txt");
RuntimeInfo.DefaultSettingsFileName = "SebClientSettings.seb";
RuntimeInfo.ProgramCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;
RuntimeInfo.ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser));
RuntimeInfo.ProgramTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
RuntimeInfo.ProgramVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
RuntimeInfo.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.txt");
}
private void Initialize(ILogger logger, IRuntimeInfo runtimeInfo)
private void InitializeLogging()
{
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), runtimeInfo.RuntimeLogFile);
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), RuntimeInfo.RuntimeLogFile);
logFileWriter.Initialize();
logger.Subscribe(logFileWriter);
Logger.Subscribe(logFileWriter);
}
}
}