From 6e44da94975a62b9aeb7ef50dcdd956b2441e47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= Date: Fri, 7 Jul 2017 15:46:32 +0200 Subject: [PATCH] Continued scaffolding: - Added splash screen - Completed logger - Added startup and shutdown components --- .../Configuration/ISettings.cs | 1 + .../Configuration/IShutdownController.cs | 15 ++++ .../Configuration/IStartupController.cs | 17 +++++ SafeExamBrowser.Contracts/I18n/Key.cs | 6 +- .../Logging/ILogContent.cs | 16 ++++ .../Logging/ILogMessage.cs | 3 +- .../Logging/ILogObserver.cs | 2 +- SafeExamBrowser.Contracts/Logging/ILogText.cs | 15 ++++ SafeExamBrowser.Contracts/Logging/ILogger.cs | 6 +- .../SafeExamBrowser.Contracts.csproj | 8 ++ .../UserInterface/IMessageBox.cs | 15 ++++ .../UserInterface/ISplashScreen.cs | 18 +++++ .../UserInterface/MessageBoxAction.cs | 15 ++++ .../UserInterface/MessageBoxIcon.cs | 17 +++++ .../Logging/LoggerTests.cs | 36 ++++----- .../Configuration/Settings.cs | 23 ++++++ .../Configuration/ShutdownController.cs | 44 +++++++++++ .../Configuration/StartupController.cs | 75 +++++++++++++++++++ SafeExamBrowser.Core/I18n/Text.xml | 4 + SafeExamBrowser.Core/Logging/LogFileWriter.cs | 37 +++++++-- SafeExamBrowser.Core/Logging/LogMessage.cs | 6 +- SafeExamBrowser.Core/Logging/LogText.cs | 27 +++++++ SafeExamBrowser.Core/Logging/Logger.cs | 71 ++++++++++++++---- .../SafeExamBrowser.Core.csproj | 3 + .../SafeExamBrowser.UserInterface.csproj | 8 ++ .../SplashScreen.xaml | 25 +++++++ .../SplashScreen.xaml.cs | 27 +++++++ SafeExamBrowser.UserInterface/Taskbar.xaml | 2 +- .../WpfMessageBox.cs | 43 +++++++++++ SafeExamBrowser/App.cs | 39 ++++++---- SafeExamBrowser/CompositionRoot.cs | 25 ++++--- SafeExamBrowser/Properties/AssemblyInfo.cs | 20 ++--- 32 files changed, 584 insertions(+), 85 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Configuration/IShutdownController.cs create mode 100644 SafeExamBrowser.Contracts/Configuration/IStartupController.cs create mode 100644 SafeExamBrowser.Contracts/Logging/ILogContent.cs create mode 100644 SafeExamBrowser.Contracts/Logging/ILogText.cs create mode 100644 SafeExamBrowser.Contracts/UserInterface/IMessageBox.cs create mode 100644 SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs create mode 100644 SafeExamBrowser.Contracts/UserInterface/MessageBoxAction.cs create mode 100644 SafeExamBrowser.Contracts/UserInterface/MessageBoxIcon.cs create mode 100644 SafeExamBrowser.Core/Configuration/ShutdownController.cs create mode 100644 SafeExamBrowser.Core/Configuration/StartupController.cs create mode 100644 SafeExamBrowser.Core/Logging/LogText.cs create mode 100644 SafeExamBrowser.UserInterface/SplashScreen.xaml create mode 100644 SafeExamBrowser.UserInterface/SplashScreen.xaml.cs create mode 100644 SafeExamBrowser.UserInterface/WpfMessageBox.cs diff --git a/SafeExamBrowser.Contracts/Configuration/ISettings.cs b/SafeExamBrowser.Contracts/Configuration/ISettings.cs index a961f0de..9eefa75a 100644 --- a/SafeExamBrowser.Contracts/Configuration/ISettings.cs +++ b/SafeExamBrowser.Contracts/Configuration/ISettings.cs @@ -11,5 +11,6 @@ namespace SafeExamBrowser.Contracts.Configuration public interface ISettings { string LogFolderPath { get; } + string LogHeader { get; } } } diff --git a/SafeExamBrowser.Contracts/Configuration/IShutdownController.cs b/SafeExamBrowser.Contracts/Configuration/IShutdownController.cs new file mode 100644 index 00000000..2acf5cdb --- /dev/null +++ b/SafeExamBrowser.Contracts/Configuration/IShutdownController.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 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 +{ + public interface IShutdownController + { + void FinalizeApplication(); + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/IStartupController.cs b/SafeExamBrowser.Contracts/Configuration/IStartupController.cs new file mode 100644 index 00000000..fc700b16 --- /dev/null +++ b/SafeExamBrowser.Contracts/Configuration/IStartupController.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 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; + +namespace SafeExamBrowser.Contracts.Configuration +{ + public interface IStartupController + { + void InitializeApplication(Action terminationCallback); + } +} diff --git a/SafeExamBrowser.Contracts/I18n/Key.cs b/SafeExamBrowser.Contracts/I18n/Key.cs index a4e92d94..7368b4f4 100644 --- a/SafeExamBrowser.Contracts/I18n/Key.cs +++ b/SafeExamBrowser.Contracts/I18n/Key.cs @@ -10,7 +10,11 @@ namespace SafeExamBrowser.Contracts.I18n { public enum Key { + MessageBox_ShutdownError, + MessageBox_ShutdownErrorTitle, MessageBox_SingleInstance, - MessageBox_SingleInstanceTitle + MessageBox_SingleInstanceTitle, + MessageBox_StartupError, + MessageBox_StartupErrorTitle } } diff --git a/SafeExamBrowser.Contracts/Logging/ILogContent.cs b/SafeExamBrowser.Contracts/Logging/ILogContent.cs new file mode 100644 index 00000000..e4755041 --- /dev/null +++ b/SafeExamBrowser.Contracts/Logging/ILogContent.cs @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2017 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; + +namespace SafeExamBrowser.Contracts.Logging +{ + public interface ILogContent : ICloneable + { + } +} diff --git a/SafeExamBrowser.Contracts/Logging/ILogMessage.cs b/SafeExamBrowser.Contracts/Logging/ILogMessage.cs index a242c890..d2a7e5e8 100644 --- a/SafeExamBrowser.Contracts/Logging/ILogMessage.cs +++ b/SafeExamBrowser.Contracts/Logging/ILogMessage.cs @@ -10,10 +10,11 @@ using System; namespace SafeExamBrowser.Contracts.Logging { - public interface ILogMessage : ICloneable + public interface ILogMessage : ILogContent { DateTime DateTime { get; } LogLevel Severity { get; } string Message { get; } + int ThreadId { get; } } } diff --git a/SafeExamBrowser.Contracts/Logging/ILogObserver.cs b/SafeExamBrowser.Contracts/Logging/ILogObserver.cs index ac33892b..25c3d58d 100644 --- a/SafeExamBrowser.Contracts/Logging/ILogObserver.cs +++ b/SafeExamBrowser.Contracts/Logging/ILogObserver.cs @@ -10,6 +10,6 @@ namespace SafeExamBrowser.Contracts.Logging { public interface ILogObserver { - void Notify(ILogMessage message); + void Notify(ILogContent content); } } diff --git a/SafeExamBrowser.Contracts/Logging/ILogText.cs b/SafeExamBrowser.Contracts/Logging/ILogText.cs new file mode 100644 index 00000000..5b3dc81d --- /dev/null +++ b/SafeExamBrowser.Contracts/Logging/ILogText.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 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.Logging +{ + public interface ILogText : ILogContent + { + string Text { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Logging/ILogger.cs b/SafeExamBrowser.Contracts/Logging/ILogger.cs index be1e5e29..8074e267 100644 --- a/SafeExamBrowser.Contracts/Logging/ILogger.cs +++ b/SafeExamBrowser.Contracts/Logging/ILogger.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.Collections.Generic; namespace SafeExamBrowser.Contracts.Logging @@ -15,8 +16,11 @@ namespace SafeExamBrowser.Contracts.Logging void Info(string message); void Warn(string message); void Error(string message); + void Error(string message, Exception exception); + void Log(string message); + void Log(ILogContent content); void Subscribe(ILogObserver observer); void Unsubscribe(ILogObserver observer); - IList GetLog(); + IList GetLog(); } } diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index bbadca59..c044e09c 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -41,15 +41,23 @@ + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.Contracts/UserInterface/IMessageBox.cs b/SafeExamBrowser.Contracts/UserInterface/IMessageBox.cs new file mode 100644 index 00000000..d5181d98 --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/IMessageBox.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 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 interface IMessageBox + { + void Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information); + } +} diff --git a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs new file mode 100644 index 00000000..9e9a5205 --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2017 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.Logging; + +namespace SafeExamBrowser.Contracts.UserInterface +{ + public interface ISplashScreen : ILogObserver + { + void Show(); + void Close(); + } +} diff --git a/SafeExamBrowser.Contracts/UserInterface/MessageBoxAction.cs b/SafeExamBrowser.Contracts/UserInterface/MessageBoxAction.cs new file mode 100644 index 00000000..b61d961f --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/MessageBoxAction.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 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 MessageBoxAction + { + Confirm + } +} diff --git a/SafeExamBrowser.Contracts/UserInterface/MessageBoxIcon.cs b/SafeExamBrowser.Contracts/UserInterface/MessageBoxIcon.cs new file mode 100644 index 00000000..548e8494 --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/MessageBoxIcon.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 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 MessageBoxIcon + { + Information, + Warning, + Error + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs b/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs index ae8d279a..94acaf1c 100644 --- a/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs @@ -34,14 +34,14 @@ namespace SafeExamBrowser.Core.UnitTests.Logging Assert.IsTrue(log.Count == 3); - Assert.IsTrue(info.Equals(log[0].Message)); - Assert.IsTrue(log[0].Severity == LogLevel.Info); + Assert.IsTrue(info.Equals((log[0] as ILogMessage).Message)); + Assert.IsTrue((log[0] as ILogMessage).Severity == LogLevel.Info); - Assert.IsTrue(warn.Equals(log[1].Message)); - Assert.IsTrue(log[1].Severity == LogLevel.Warn); + Assert.IsTrue(warn.Equals((log[1] as ILogMessage).Message)); + Assert.IsTrue((log[1] as ILogMessage).Severity == LogLevel.Warn); - Assert.IsTrue(error.Equals(log[2].Message)); - Assert.IsTrue(log[2].Severity == LogLevel.Error); + Assert.IsTrue(error.Equals((log[2] as ILogMessage).Message)); + Assert.IsTrue((log[2] as ILogMessage).Severity == LogLevel.Error); } [TestMethod] @@ -107,23 +107,23 @@ namespace SafeExamBrowser.Core.UnitTests.Logging var sut = new Logger(); var observer = new Mock(); var message = "Blubb"; - var messages = new List(); + var messages = new List(); - observer.Setup(o => o.Notify(It.IsAny())).Callback(m => messages.Add(m)); + observer.Setup(o => o.Notify(It.IsAny())).Callback(m => messages.Add(m)); sut.Subscribe(observer.Object); sut.Info(message); sut.Warn(message); - observer.Verify(o => o.Notify(It.IsAny()), Times.Exactly(2)); + observer.Verify(o => o.Notify(It.IsAny()), Times.Exactly(2)); Assert.IsTrue(messages.Count == 2); - Assert.IsTrue(messages[0].Severity == LogLevel.Info); - Assert.IsTrue(message.Equals(messages[0].Message)); + Assert.IsTrue((messages[0] as ILogMessage).Severity == LogLevel.Info); + Assert.IsTrue(message.Equals((messages[0] as ILogMessage).Message)); - Assert.IsTrue(messages[1].Severity == LogLevel.Warn); - Assert.IsTrue(message.Equals(messages[1].Message)); + Assert.IsTrue((messages[1] as ILogMessage).Severity == LogLevel.Warn); + Assert.IsTrue(message.Equals((messages[1] as ILogMessage).Message)); } [TestMethod] @@ -132,21 +132,21 @@ namespace SafeExamBrowser.Core.UnitTests.Logging var sut = new Logger(); var observer = new Mock(); var message = "Blubb"; - var messages = new List(); + var messages = new List(); - observer.Setup(o => o.Notify(It.IsAny())).Callback(m => messages.Add(m)); + observer.Setup(o => o.Notify(It.IsAny())).Callback(m => messages.Add(m)); sut.Subscribe(observer.Object); sut.Info(message); sut.Unsubscribe(observer.Object); sut.Warn(message); - observer.Verify(o => o.Notify(It.IsAny()), Times.Once()); + observer.Verify(o => o.Notify(It.IsAny()), Times.Once()); Assert.IsTrue(messages.Count == 1); - Assert.IsTrue(messages[0].Severity == LogLevel.Info); - Assert.IsTrue(message.Equals(messages[0].Message)); + Assert.IsTrue((messages[0] as ILogMessage).Severity == LogLevel.Info); + Assert.IsTrue(message.Equals((messages[0] as ILogMessage).Message)); } } } diff --git a/SafeExamBrowser.Core/Configuration/Settings.cs b/SafeExamBrowser.Core/Configuration/Settings.cs index c76c5095..111f08da 100644 --- a/SafeExamBrowser.Core/Configuration/Settings.cs +++ b/SafeExamBrowser.Core/Configuration/Settings.cs @@ -8,6 +8,7 @@ using System; using System.IO; +using System.Reflection; using SafeExamBrowser.Contracts.Configuration; namespace SafeExamBrowser.Core.Configuration @@ -21,5 +22,27 @@ namespace SafeExamBrowser.Core.Configuration return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SafeExamBrowser", "Logs"); } } + + public string LogHeader + { + get + { + var executable = Assembly.GetEntryAssembly(); + var newline = Environment.NewLine; + var version = executable.GetCustomAttribute().InformationalVersion; + var title = executable.GetCustomAttribute().Title; + var copyright = executable.GetCustomAttribute().Copyright; + + var titleLine = $"/* {title}, Version {version}{newline}"; + var copyrightLine = $"/* {copyright}{newline}"; + var emptyLine = $"/* {newline}"; + var license1 = $"/* The source code of this application is subject to the terms of the Mozilla Public{newline}"; + var license2 = $"/* License, v. 2.0. If a copy of the MPL was not distributed with this software, You{newline}"; + var license3 = $"/* can obtain one at http://mozilla.org/MPL/2.0/.{newline}"; + var github = $"/* For more information or to issue bug reports, see https://github.com/SafeExamBrowser.{newline}"; + + return $"{titleLine}{copyrightLine}{emptyLine}{license1}{license2}{license3}{emptyLine}{github}"; + } + } } } diff --git a/SafeExamBrowser.Core/Configuration/ShutdownController.cs b/SafeExamBrowser.Core/Configuration/ShutdownController.cs new file mode 100644 index 00000000..c46bb32e --- /dev/null +++ b/SafeExamBrowser.Core/Configuration/ShutdownController.cs @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 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 SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; + +namespace SafeExamBrowser.Core.Configuration +{ + public class ShutdownController : IShutdownController + { + private ILogger logger; + private IMessageBox messageBox; + private IText text; + + public ShutdownController(ILogger logger, IMessageBox messageBox, IText text) + { + this.logger = logger; + this.messageBox = messageBox; + this.text = text; + } + + public void FinalizeApplication() + { + try + { + // TODO: + // - Gather TODOs! + } + catch (Exception e) + { + logger.Error($"Failed to finalize application!", e); + messageBox.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error); + } + } + } +} diff --git a/SafeExamBrowser.Core/Configuration/StartupController.cs b/SafeExamBrowser.Core/Configuration/StartupController.cs new file mode 100644 index 00000000..5d579f88 --- /dev/null +++ b/SafeExamBrowser.Core/Configuration/StartupController.cs @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 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.Threading; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; + +namespace SafeExamBrowser.Core.Configuration +{ + public class StartupController : IStartupController + { + private ILogger logger; + private IMessageBox messageBox; + private ISplashScreen splashScreen; + private IText text; + + public StartupController(ILogger logger, IMessageBox messageBox, ISplashScreen splashScreen, IText text) + { + this.logger = logger; + this.messageBox = messageBox; + this.splashScreen = splashScreen; + this.text = text; + } + + public void InitializeApplication(Action terminationCallback) + { + try + { + logger.Info("Rendering splash screen."); + logger.Subscribe(splashScreen); + splashScreen.Show(); + + // TODO (depending on specification): + // - WCF service connection, termination if not available + + // TODO: + // - Parse command line arguments + // - Detecting operating system and logging information + // - Logging of all running processes + // - Setting of wallpaper + // - Initialization of taskbar + // - Killing explorer.exer + // - Minimizing all open windows + // - Emptying clipboard + // - Activation of process monitoring + + Thread.Sleep(3000); + + logger.Info("Baapa-dee boopa-dee!"); + + Thread.Sleep(3000); + + logger.Info("Closing splash screen."); + logger.Unsubscribe(splashScreen); + splashScreen.Close(); + + logger.Info("Application successfully initialized!"); + } + catch (Exception e) + { + logger.Error($"Failed to initialize application!", e); + messageBox.Show(text.Get(Key.MessageBox_StartupError), text.Get(Key.MessageBox_StartupErrorTitle), icon: MessageBoxIcon.Error); + terminationCallback?.Invoke(); + } + } + } +} diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml index 5c279b7a..f25c4eb0 100644 --- a/SafeExamBrowser.Core/I18n/Text.xml +++ b/SafeExamBrowser.Core/I18n/Text.xml @@ -1,5 +1,9 @@  + An unexpected error occurred during the shutdown procedure! Please consult the application log for more information... + Shutdown Error You can only run one instance of SEB at a time. Startup Not Allowed + An unexpected error occurred during the startup procedure! Please consult the application log for more information... + Startup Error \ No newline at end of file diff --git a/SafeExamBrowser.Core/Logging/LogFileWriter.cs b/SafeExamBrowser.Core/Logging/LogFileWriter.cs index 419bdc7f..0ac5dc7a 100644 --- a/SafeExamBrowser.Core/Logging/LogFileWriter.cs +++ b/SafeExamBrowser.Core/Logging/LogFileWriter.cs @@ -9,7 +9,6 @@ using System; using System.IO; using System.Text; -using System.Threading; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Logging; @@ -22,7 +21,7 @@ namespace SafeExamBrowser.Core.Logging public LogFileWriter(ISettings settings) { - var fileName = $"{DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss")}.txt"; + var fileName = $"{DateTime.Now.ToString("yyyy-MM-dd HH\\hmm\\mss\\s")}.txt"; if (!Directory.Exists(settings.LogFolderPath)) { @@ -32,17 +31,39 @@ namespace SafeExamBrowser.Core.Logging filePath = Path.Combine(settings.LogFolderPath, fileName); } - public void Notify(ILogMessage message) + public void Notify(ILogContent content) + { + if (content is ILogText) + { + WriteLogText(content as ILogText); + } + + if (content is ILogMessage) + { + WriteLogMessage(content as ILogMessage); + } + } + + private void WriteLogText(ILogText text) + { + Write(text.Text); + } + + private void WriteLogMessage(ILogMessage message) + { + var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); + var severity = message.Severity.ToString().ToUpper(); + + Write($"{date} [{message.ThreadId}] - {severity}: {message.Message}"); + } + + private void Write(string content) { lock (@lock) { using (var stream = new StreamWriter(filePath, true, Encoding.UTF8)) { - var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss"); - var threadId = Thread.CurrentThread.ManagedThreadId; - var severity = message.Severity.ToString().ToUpper(); - - stream.WriteLine($"{date} [{threadId}] - {severity}: {message.Message}"); + stream.WriteLine(content); } } } diff --git a/SafeExamBrowser.Core/Logging/LogMessage.cs b/SafeExamBrowser.Core/Logging/LogMessage.cs index 12005733..e4301f8a 100644 --- a/SafeExamBrowser.Core/Logging/LogMessage.cs +++ b/SafeExamBrowser.Core/Logging/LogMessage.cs @@ -16,17 +16,19 @@ namespace SafeExamBrowser.Core.Entities public DateTime DateTime { get; private set; } public LogLevel Severity { get; private set; } public string Message { get; private set; } + public int ThreadId { get; private set; } - public LogMessage(DateTime dateTime, LogLevel severity, string message) + public LogMessage(DateTime dateTime, LogLevel severity, int threadId, string message) { DateTime = dateTime; Severity = severity; Message = message; + ThreadId = threadId; } public object Clone() { - return new LogMessage(DateTime, Severity, Message); + return new LogMessage(DateTime, Severity, ThreadId, Message); } } } diff --git a/SafeExamBrowser.Core/Logging/LogText.cs b/SafeExamBrowser.Core/Logging/LogText.cs new file mode 100644 index 00000000..487436e3 --- /dev/null +++ b/SafeExamBrowser.Core/Logging/LogText.cs @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 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.Logging; + +namespace SafeExamBrowser.Core.Logging +{ + public class LogText : ILogText + { + public string Text { get; private set; } + + public LogText(string text) + { + Text = text; + } + + public object Clone() + { + return new LogText(Text); + } + } +} diff --git a/SafeExamBrowser.Core/Logging/Logger.cs b/SafeExamBrowser.Core/Logging/Logger.cs index 2474e200..edae921e 100644 --- a/SafeExamBrowser.Core/Logging/Logger.cs +++ b/SafeExamBrowser.Core/Logging/Logger.cs @@ -9,6 +9,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Core.Entities; @@ -17,29 +19,63 @@ namespace SafeExamBrowser.Core.Logging public class Logger : ILogger { private static readonly object @lock = new object(); - private readonly IList log = new List(); + private readonly IList log = new List(); private readonly IList observers = new List(); - public void Error(string message) - { - Log(LogLevel.Error, message); - } - public void Info(string message) { - Log(LogLevel.Info, message); + Add(LogLevel.Info, message); } public void Warn(string message) { - Log(LogLevel.Warn, message); + Add(LogLevel.Warn, message); } - public IList GetLog() + public void Error(string message) + { + Add(LogLevel.Error, message); + } + + public void Error(string message, Exception exception) + { + var details = new StringBuilder(); + + details.AppendLine(); + details.AppendLine($" Exception Message: {exception.Message}"); + details.AppendLine($" Exception Type: {exception.GetType()}"); + details.AppendLine(); + details.AppendLine(exception.StackTrace); + + Add(LogLevel.Error, message); + Add(new LogText(details.ToString())); + } + + public void Log(string text) + { + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + + Add(new LogText(text)); + } + + public void Log(ILogContent content) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + Add(content.Clone() as ILogContent); + } + + public IList GetLog() { lock (@lock) { - return log.Select(m => m.Clone() as ILogMessage).ToList(); + return log.Select(m => m.Clone() as ILogContent).ToList(); } } @@ -67,17 +103,22 @@ namespace SafeExamBrowser.Core.Logging } } - private void Log(LogLevel severity, string message) + private void Add(LogLevel severity, string message) + { + var threadId = Thread.CurrentThread.ManagedThreadId; + + Add(new LogMessage(DateTime.Now, severity, threadId, message)); + } + + private void Add(ILogContent content) { lock (@lock) { - var entry = new LogMessage(DateTime.Now, severity, message); + log.Add(content); - log.Add(entry); - foreach (var observer in observers) { - observer.Notify(entry); + observer.Notify(content); } } } diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index 844d596b..c7e4f48a 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -41,11 +41,14 @@ + + + diff --git a/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj b/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj index 9a82b20f..acb4423c 100644 --- a/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj +++ b/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj @@ -60,9 +60,13 @@ Settings.settings True + + SplashScreen.xaml + Taskbar.xaml + ResXFileCodeGenerator Resources.Designer.cs @@ -73,6 +77,10 @@ + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml b/SafeExamBrowser.UserInterface/SplashScreen.xaml new file mode 100644 index 00000000..6f04d7f1 --- /dev/null +++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs new file mode 100644 index 00000000..9cbd9209 --- /dev/null +++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 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.Windows; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; + +namespace SafeExamBrowser.UserInterface +{ + public partial class SplashScreen : Window, ISplashScreen + { + public SplashScreen() + { + InitializeComponent(); + } + + public void Notify(ILogContent content) + { + // TODO + } + } +} diff --git a/SafeExamBrowser.UserInterface/Taskbar.xaml b/SafeExamBrowser.UserInterface/Taskbar.xaml index 6da0624a..df082d53 100644 --- a/SafeExamBrowser.UserInterface/Taskbar.xaml +++ b/SafeExamBrowser.UserInterface/Taskbar.xaml @@ -7,7 +7,7 @@ mc:Ignorable="d" Title="Taskbar" Height="40" Width="300" WindowStyle="None" AllowsTransparency="True" Topmost="True"> - + diff --git a/SafeExamBrowser.UserInterface/WpfMessageBox.cs b/SafeExamBrowser.UserInterface/WpfMessageBox.cs new file mode 100644 index 00000000..64061903 --- /dev/null +++ b/SafeExamBrowser.UserInterface/WpfMessageBox.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 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.Windows; +using SafeExamBrowser.Contracts.UserInterface; + +namespace SafeExamBrowser.UserInterface +{ + public class WpfMessageBox : IMessageBox + { + public void Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information) + { + MessageBox.Show(message, title, ToButton(action), ToImage(icon)); + } + + private MessageBoxButton ToButton(MessageBoxAction action) + { + switch (action) + { + default: + return MessageBoxButton.OK; + } + } + + private MessageBoxImage ToImage(MessageBoxIcon icon) + { + switch (icon) + { + case MessageBoxIcon.Warning: + return MessageBoxImage.Warning; + case MessageBoxIcon.Error: + return MessageBoxImage.Error; + default: + return MessageBoxImage.Information; + } + } + } +} diff --git a/SafeExamBrowser/App.cs b/SafeExamBrowser/App.cs index 8b5386b6..64d61499 100644 --- a/SafeExamBrowser/App.cs +++ b/SafeExamBrowser/App.cs @@ -15,19 +15,14 @@ namespace SafeExamBrowser { public class App : Application { - private static Mutex mutex = new Mutex(true, "safe_exam_browser_single_instance_mutex"); + private static readonly Mutex mutex = new Mutex(true, "safe_exam_browser_single_instance_mutex"); [STAThread] public static void Main() { try { - var compositionRoot = new CompositionRoot(); - - compositionRoot.InitializeGlobalModules(); - compositionRoot.BuildObjectGraph(); - - StartApplication(compositionRoot); + StartApplication(); } catch (Exception e) { @@ -35,24 +30,36 @@ namespace SafeExamBrowser } } - private static void StartApplication(CompositionRoot compositionRoot) + private static void StartApplication() { - compositionRoot.Logger.Info("Testing the log..."); + var root = new CompositionRoot(); - if (NoInstanceRunning()) + root.BuildObjectGraph(); + + root.Logger.Log(root.Settings.LogHeader); + root.Logger.Log($"# Application started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}{Environment.NewLine}"); + + if (NoInstanceRunning(root)) { - new App().Run(compositionRoot.Taskbar); + var app = new App(); + + root.Logger.Info("No instance is running, initiating startup procedure."); + + app.Startup += (o, args) => root.StartupController.InitializeApplication(app.Shutdown); + app.Exit += (o, args) => root.ShutdownController.FinalizeApplication(); + + app.Run(root.Taskbar); } else { - var message = compositionRoot.Text.Get(Key.MessageBox_SingleInstance); - var title = compositionRoot.Text.Get(Key.MessageBox_SingleInstanceTitle); - - MessageBox.Show(message, title); + root.Logger.Info("Could not start because of an already running instance."); + root.MessageBox.Show(root.Text.Get(Key.MessageBox_SingleInstance), root.Text.Get(Key.MessageBox_SingleInstanceTitle)); } + + root.Logger.Log($"# Application terminating normally at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } - private static bool NoInstanceRunning() + private static bool NoInstanceRunning(CompositionRoot root) { return mutex.WaitOne(TimeSpan.Zero, true); } diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs index 6b8451a2..edf88c98 100644 --- a/SafeExamBrowser/CompositionRoot.cs +++ b/SafeExamBrowser/CompositionRoot.cs @@ -10,6 +10,7 @@ using System.Windows; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Core.Configuration; using SafeExamBrowser.Core.I18n; using SafeExamBrowser.Core.Logging; @@ -17,24 +18,30 @@ using SafeExamBrowser.UserInterface; namespace SafeExamBrowser { - class CompositionRoot + internal class CompositionRoot { public ILogger Logger { get; private set; } + public IMessageBox MessageBox { get; private set; } public ISettings Settings { get; private set; } + public IShutdownController ShutdownController { get; set; } + public ISplashScreen SplashScreen { get; private set; } + public IStartupController StartupController { get; private set; } public IText Text { get; private set; } public Window Taskbar { get; private set; } - public void InitializeGlobalModules() - { - Settings = new Settings(); - Logger = new Logger(); - Logger.Subscribe(new LogFileWriter(Settings)); - Text = new Text(new XmlTextResource()); - } - public void BuildObjectGraph() { + MessageBox = new WpfMessageBox(); + Settings = new Settings(); + SplashScreen = new UserInterface.SplashScreen(); Taskbar = new Taskbar(); + Text = new Text(new XmlTextResource()); + + Logger = new Logger(); + Logger.Subscribe(new LogFileWriter(Settings)); + + ShutdownController = new ShutdownController(Logger, MessageBox, Text); + StartupController = new StartupController(Logger, MessageBox, SplashScreen, Text); } } } diff --git a/SafeExamBrowser/Properties/AssemblyInfo.cs b/SafeExamBrowser/Properties/AssemblyInfo.cs index 66d4320b..372c1fc5 100644 --- a/SafeExamBrowser/Properties/AssemblyInfo.cs +++ b/SafeExamBrowser/Properties/AssemblyInfo.cs @@ -1,20 +1,15 @@ using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("SafeExamBrowser")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SafeExamBrowser")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] +[assembly: AssemblyTitle("Safe Exam Browser")] +[assembly: AssemblyDescription("Safe Exam Browser")] +[assembly: AssemblyCompany("ETH Zürich")] +[assembly: AssemblyProduct("Safe Exam Browser")] +[assembly: AssemblyCopyright("Copyright © 2017 ETH Zürich, Educational Development and Technology (LET)")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -51,5 +46,6 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("3.0.0")] +[assembly: AssemblyFileVersion("3.0.0")] +[assembly: AssemblyInformationalVersion("3.0.0 (beta)")]