SEBWIN-219: Introduced configuration mode and continued with configuration operation.

This commit is contained in:
dbuechel 2018-01-19 09:23:09 +01:00
parent 39261f5222
commit 57c3afdaa5
16 changed files with 168 additions and 43 deletions

View file

@ -14,17 +14,19 @@ namespace SafeExamBrowser.Configuration.Settings
[Serializable]
internal class Settings : ISettings
{
public IBrowserSettings Browser { get; private set; }
public IKeyboardSettings Keyboard { get; private set; }
public IMouseSettings Mouse { get; private set; }
public ITaskbarSettings Taskbar { get; private set; }
public ConfigurationMode ConfigurationMode { get; set; }
public Settings(BrowserSettings browser, KeyboardSettings keyboard, MouseSettings mouse, TaskbarSettings taskbar)
public IBrowserSettings Browser { get; set; }
public IKeyboardSettings Keyboard { get; set; }
public IMouseSettings Mouse { get; set; }
public ITaskbarSettings Taskbar { get; set; }
public Settings()
{
Browser = browser;
Keyboard = keyboard;
Mouse = mouse;
Taskbar = taskbar;
Browser = new BrowserSettings();
Keyboard = new KeyboardSettings();
Mouse = new MouseSettings();
Taskbar = new TaskbarSettings();
}
}
}

View file

@ -16,16 +16,12 @@ namespace SafeExamBrowser.Configuration.Settings
public ISettings Load(Uri path)
{
// TODO
throw new NotImplementedException();
return LoadDefaults();
}
public ISettings LoadDefaults()
{
var browser = new BrowserSettings();
var keyboard = new KeyboardSettings();
var mouse = new MouseSettings();
var taskbar = new TaskbarSettings();
var settings = new Settings(browser, keyboard, mouse, taskbar);
var settings = new Settings();
// TODO

View file

@ -0,0 +1,16 @@
/*
* 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.Configuration.Settings
{
public enum ConfigurationMode
{
ConfigureClient,
Exam
}
}

View file

@ -10,6 +10,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
{
public interface ISettings
{
/// <summary>
/// The mode which determines the configuration behaviour.
/// </summary>
ConfigurationMode ConfigurationMode { get; }
/// <summary>
/// All browser-related settings.
/// </summary>

View file

@ -15,6 +15,8 @@ namespace SafeExamBrowser.Contracts.I18n
{
Browser_ShowDeveloperConsole,
LogWindow_Title,
MessageBox_ConfigureClientSuccess,
MessageBox_ConfigureClientSuccessTitle,
MessageBox_ShutdownError,
MessageBox_ShutdownErrorTitle,
MessageBox_SingleInstance,

View file

@ -54,6 +54,7 @@
<ItemGroup>
<Compile Include="Behaviour\IApplicationController.cs" />
<Compile Include="Configuration\IRuntimeInfo.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" />
<Compile Include="Runtime\IRuntimeController.cs" />
<Compile Include="Behaviour\INotificationController.cs" />
<Compile Include="Behaviour\IOperation.cs" />
@ -98,6 +99,7 @@
<Compile Include="UserInterface\IBrowserControl.cs" />
<Compile Include="UserInterface\IBrowserWindow.cs" />
<Compile Include="UserInterface\IMessageBox.cs" />
<Compile Include="UserInterface\MessageBoxResult.cs" />
<Compile Include="UserInterface\Taskbar\INotificationButton.cs" />
<Compile Include="UserInterface\ISplashScreen.cs" />
<Compile Include="UserInterface\Taskbar\ISystemKeyboardLayoutControl.cs" />

View file

@ -11,8 +11,8 @@ namespace SafeExamBrowser.Contracts.UserInterface
public interface IMessageBox
{
/// <summary>
/// Shows a message box according to the specified parameters.
/// Shows a message box according to the specified parameters and returns the result chosen by the user.
/// </summary>
void 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);
}
}

View file

@ -13,6 +13,7 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
public enum MessageBoxAction
{
Confirm
Confirm,
YesNo
}
}

View file

@ -13,8 +13,9 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
public enum MessageBoxIcon
{
Error,
Information,
Warning,
Error
Question,
Warning
}
}

View file

@ -0,0 +1,19 @@
/*
* 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.UserInterface
{
public enum MessageBoxResult
{
None = 0,
Cancel,
No,
Ok,
Yes
}
}

View file

@ -2,6 +2,8 @@
<Text>
<Browser_ShowDeveloperConsole>Open Console</Browser_ShowDeveloperConsole>
<LogWindow_Title>Application Log</LogWindow_Title>
<MessageBox_ConfigureClientSuccess>The client configuration has been saved and will be used when you start the application the next time. Do you want to quit for now?</MessageBox_ConfigureClientSuccess>
<MessageBox_ConfigureClientSuccessTitle>Configuration Successful</MessageBox_ConfigureClientSuccessTitle>
<MessageBox_ShutdownError>An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...</MessageBox_ShutdownError>
<MessageBox_ShutdownErrorTitle>Shutdown Error</MessageBox_ShutdownErrorTitle>
<MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError>

View file

@ -12,6 +12,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Runtime;
using SafeExamBrowser.Contracts.UserInterface;
@ -26,8 +27,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
private Mock<IRuntimeController> controller;
private Mock<IRuntimeInfo> info;
private Mock<ISettingsRepository> repository;
private Mock<ISettings> settings;
private Mock<ISplashScreen> splashScreen;
private Mock<IText> text;
private Mock<IUserInterfaceFactory> uiFactory;
private ConfigurationOperation sut;
[TestInitialize]
@ -37,11 +40,16 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
controller = new Mock<IRuntimeController>();
info = new Mock<IRuntimeInfo>();
repository = new Mock<ISettingsRepository>();
settings = new Mock<ISettings>();
splashScreen = new Mock<ISplashScreen>();
text = new Mock<IText>();
uiFactory = new Mock<IUserInterfaceFactory>();
info.SetupGet(r => r.AppDataFolder).Returns(@"C:\Not\Really\AppData");
info.SetupGet(r => r.DefaultSettingsFileName).Returns("SettingsDummy.txt");
info.SetupGet(r => r.ProgramDataFolder).Returns(@"C:\Not\Really\ProgramData");
info.SetupGet(i => i.AppDataFolder).Returns(@"C:\Not\Really\AppData");
info.SetupGet(i => i.DefaultSettingsFileName).Returns("SettingsDummy.txt");
info.SetupGet(i => i.ProgramDataFolder).Returns(@"C:\Not\Really\ProgramData");
repository.Setup(r => r.Load(It.IsAny<Uri>())).Returns(settings.Object);
repository.Setup(r => r.LoadDefaults()).Returns(settings.Object);
}
[TestMethod]
@ -49,14 +57,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{
controller.SetupSet(c => c.Settings = It.IsAny<ISettings>());
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, null)
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null)
{
SplashScreen = splashScreen.Object
};
sut.Perform();
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, new string[] { })
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new string[] { })
{
SplashScreen = splashScreen.Object
};
@ -71,7 +79,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{
var path = @"an/invalid\path.'*%yolo/()";
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, new [] { "blubb.exe", path })
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new [] { "blubb.exe", path })
{
SplashScreen = splashScreen.Object
};
@ -88,7 +96,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
info.SetupGet(r => r.ProgramDataFolder).Returns(location);
info.SetupGet(r => r.AppDataFolder).Returns(location);
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, new[] { "blubb.exe", path })
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", path })
{
SplashScreen = splashScreen.Object
};
@ -107,7 +115,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
info.SetupGet(r => r.ProgramDataFolder).Returns(location);
info.SetupGet(r => r.AppDataFolder).Returns($@"{location}\WRONG");
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, null)
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null)
{
SplashScreen = splashScreen.Object
};
@ -125,7 +133,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
info.SetupGet(r => r.AppDataFolder).Returns(location);
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, null)
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null)
{
SplashScreen = splashScreen.Object
};
@ -139,7 +147,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustFallbackToDefaultsAsLastPrio()
{
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, null)
sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null)
{
SplashScreen = splashScreen.Object
};

View file

@ -24,6 +24,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private IRuntimeController controller;
private IRuntimeInfo runtimeInfo;
private ISettingsRepository repository;
private IText text;
private IUserInterfaceFactory uiFactory;
private string[] commandLineArgs;
public ISplashScreen SplashScreen { private get; set; }
@ -33,6 +35,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
IRuntimeController controller,
IRuntimeInfo runtimeInfo,
ISettingsRepository repository,
IText text,
IUserInterfaceFactory uiFactory,
string[] commandLineArgs)
{
this.logger = logger;
@ -40,6 +44,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
this.commandLineArgs = commandLineArgs;
this.repository = repository;
this.runtimeInfo = runtimeInfo;
this.text = text;
this.uiFactory = uiFactory;
}
public void Perform()
@ -47,20 +53,33 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info("Initializing application configuration...");
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeConfiguration);
ISettings settings;
var isValidUri = TryGetSettingsUri(out Uri uri);
if (isValidUri)
{
logger.Info($"Loading configuration from '{uri.AbsolutePath}'...");
controller.Settings = repository.Load(uri);
settings = repository.Load(uri);
}
else
{
logger.Info("No valid settings file specified nor found in PROGRAMDATA or APPDATA - loading default settings...");
controller.Settings = repository.LoadDefaults();
settings = repository.LoadDefaults();
}
// TODO: Allow user to quit if in Configure Client mode - callback to terminate WPF application?
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;
}
public void Revert()

View file

@ -53,7 +53,7 @@ namespace SafeExamBrowser.Runtime
StartupOperations = new Queue<IOperation>();
StartupOperations.Enqueue(new I18nOperation(logger, text));
StartupOperations.Enqueue(new ConfigurationOperation(logger, runtimeController, runtimeInfo, settingsRepository, args));
StartupOperations.Enqueue(new ConfigurationOperation(logger, runtimeController, runtimeInfo, settingsRepository, text, uiFactory, args));
//StartupOperations.Enqueue(new KioskModeOperation());
StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, logger));
}

View file

@ -15,6 +15,7 @@ using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.UserInterface.Classic.Controls;
using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBoxResult;
namespace SafeExamBrowser.UserInterface.Classic
{
@ -105,15 +106,21 @@ namespace SafeExamBrowser.UserInterface.Classic
return new WirelessNetworkControl();
}
public void 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)
{
MessageBox.Show(message, title, ToButton(action), ToImage(icon));
// The last two parameters are an unfortunate necessity, since e.g. splash screens are displayed topmost while running in their
// own thread / dispatcher, and would thus conceal the message box...
var result = MessageBox.Show(message, title, ToButton(action), ToImage(icon), System.Windows.MessageBoxResult.None, MessageBoxOptions.ServiceNotification);
return ToResult(result);
}
private MessageBoxButton ToButton(MessageBoxAction action)
{
switch (action)
{
case MessageBoxAction.YesNo:
return MessageBoxButton.YesNo;
default:
return MessageBoxButton.OK;
}
@ -123,13 +130,32 @@ namespace SafeExamBrowser.UserInterface.Classic
{
switch (icon)
{
case MessageBoxIcon.Warning:
return MessageBoxImage.Warning;
case MessageBoxIcon.Error:
return MessageBoxImage.Error;
case MessageBoxIcon.Question:
return MessageBoxImage.Question;
case MessageBoxIcon.Warning:
return MessageBoxImage.Warning;
default:
return MessageBoxImage.Information;
}
}
private MessageBoxResult ToResult(System.Windows.MessageBoxResult result)
{
switch (result)
{
case System.Windows.MessageBoxResult.Cancel:
return MessageBoxResult.Cancel;
case System.Windows.MessageBoxResult.No:
return MessageBoxResult.No;
case System.Windows.MessageBoxResult.OK:
return MessageBoxResult.Ok;
case System.Windows.MessageBoxResult.Yes:
return MessageBoxResult.Yes;
default:
return MessageBoxResult.None;
}
}
}
}

View file

@ -15,6 +15,7 @@ using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.UserInterface.Windows10.Controls;
using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBoxResult;
namespace SafeExamBrowser.UserInterface.Windows10
{
@ -107,15 +108,21 @@ namespace SafeExamBrowser.UserInterface.Windows10
throw new System.NotImplementedException();
}
public void 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)
{
MessageBox.Show(message, title, ToButton(action), ToImage(icon));
// The last two parameters are an unfortunate necessity, since e.g. splash screens are displayed topmost while running in their
// own thread / dispatcher, and would thus conceal the message box...
var result = MessageBox.Show(message, title, ToButton(action), ToImage(icon), System.Windows.MessageBoxResult.None, MessageBoxOptions.ServiceNotification);
return ToResult(result);
}
private MessageBoxButton ToButton(MessageBoxAction action)
{
switch (action)
{
case MessageBoxAction.YesNo:
return MessageBoxButton.YesNo;
default:
return MessageBoxButton.OK;
}
@ -125,13 +132,32 @@ namespace SafeExamBrowser.UserInterface.Windows10
{
switch (icon)
{
case MessageBoxIcon.Warning:
return MessageBoxImage.Warning;
case MessageBoxIcon.Error:
return MessageBoxImage.Error;
case MessageBoxIcon.Question:
return MessageBoxImage.Question;
case MessageBoxIcon.Warning:
return MessageBoxImage.Warning;
default:
return MessageBoxImage.Information;
}
}
private MessageBoxResult ToResult(System.Windows.MessageBoxResult result)
{
switch (result)
{
case System.Windows.MessageBoxResult.Cancel:
return MessageBoxResult.Cancel;
case System.Windows.MessageBoxResult.No:
return MessageBoxResult.No;
case System.Windows.MessageBoxResult.OK:
return MessageBoxResult.Ok;
case System.Windows.MessageBoxResult.Yes:
return MessageBoxResult.Yes;
default:
return MessageBoxResult.None;
}
}
}
}