diff --git a/SafeExamBrowser.Configuration/Settings.cs b/SafeExamBrowser.Configuration/Settings.cs index b5cfedc6..bfed33a7 100644 --- a/SafeExamBrowser.Configuration/Settings.cs +++ b/SafeExamBrowser.Configuration/Settings.cs @@ -29,7 +29,7 @@ namespace SafeExamBrowser.Configuration public bool AllowApplicationLog => true; public bool AllowKeyboardLayout => true; - public string AppDataFolderName => "SafeExamBrowser"; + public string AppDataFolderName => nameof(SafeExamBrowser); public string ApplicationLogFile { diff --git a/SafeExamBrowser.Contracts/I18n/IText.cs b/SafeExamBrowser.Contracts/I18n/IText.cs index 47549c0c..9059e79f 100644 --- a/SafeExamBrowser.Contracts/I18n/IText.cs +++ b/SafeExamBrowser.Contracts/I18n/IText.cs @@ -10,9 +10,14 @@ namespace SafeExamBrowser.Contracts.I18n { public interface IText { + /// + /// Initializes the text module, e.g. loads text data from the specified text resource. + /// + void Initialize(ITextResource resource); + /// /// Gets the text associated with the specified key. If the key was not found, a default text indicating - /// that the given key is not configured shall be returned. + /// that the given key is not configured will be returned. /// string Get(TextKey key); } diff --git a/SafeExamBrowser.Contracts/I18n/ITextResource.cs b/SafeExamBrowser.Contracts/I18n/ITextResource.cs index e8f9e098..b316fb34 100644 --- a/SafeExamBrowser.Contracts/I18n/ITextResource.cs +++ b/SafeExamBrowser.Contracts/I18n/ITextResource.cs @@ -13,7 +13,7 @@ namespace SafeExamBrowser.Contracts.I18n public interface ITextResource { /// - /// Loads all text data from a resource. + /// Loads all text data from a resource. Throws an exception if the data could not be loaded, e.g. due to a data format error. /// IDictionary LoadText(); } diff --git a/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs b/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs index 8958036d..564f4afa 100644 --- a/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs +++ b/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Core.I18n; namespace SafeExamBrowser.Core.UnitTests.I18n @@ -18,14 +19,18 @@ namespace SafeExamBrowser.Core.UnitTests.I18n [TestClass] public class TextTests { + private Mock loggerMock; + + [TestInitialize] + public void Initialize() + { + loggerMock = new Mock(); + } + [TestMethod] public void MustNeverReturnNull() { - var resource = new Mock(); - var sut = new Text(resource.Object); - - resource.Setup(r => r.LoadText()).Returns>(null); - + var sut = new Text(loggerMock.Object); var text = sut.Get((TextKey)(-1)); Assert.IsNotNull(text); @@ -35,7 +40,39 @@ namespace SafeExamBrowser.Core.UnitTests.I18n [ExpectedException(typeof(ArgumentNullException))] public void MustNotAllowNullResource() { - new Text(null); + var sut = new Text(loggerMock.Object); + + sut.Initialize(null); + } + + [TestMethod] + public void MustNotFailWhenGettingNullFromResource() + { + var resource = new Mock(); + var sut = new Text(loggerMock.Object); + + resource.Setup(r => r.LoadText()).Returns>(null); + sut.Initialize(resource.Object); + + var text = sut.Get((TextKey)(-1)); + + Assert.IsNotNull(text); + } + + [TestMethod] + public void MustNotFailWhenResourceThrowsException() + { + var resource = new Mock(); + var sut = new Text(loggerMock.Object); + + resource.Setup(r => r.LoadText()).Throws(); + sut.Initialize(resource.Object); + + var text = sut.Get((TextKey)(-1)); + + loggerMock.Verify(l => l.Error(It.IsAny(), It.IsAny()), Times.AtLeastOnce); + + Assert.IsNotNull(text); } } } diff --git a/SafeExamBrowser.Core.UnitTests/I18n/Text_Incompatible.xml b/SafeExamBrowser.Core.UnitTests/I18n/Text_Incompatible.xml new file mode 100644 index 00000000..74e6710f --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/I18n/Text_Incompatible.xml @@ -0,0 +1,6 @@ + + + Some random text here... + Bappa-dee boopa-dee + + \ No newline at end of file diff --git a/SafeExamBrowser.Core.UnitTests/I18n/Text_Invalid.txt b/SafeExamBrowser.Core.UnitTests/I18n/Text_Invalid.txt new file mode 100644 index 00000000..f83886a0 --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/I18n/Text_Invalid.txt @@ -0,0 +1,13 @@ + + + Some random text here + > + + Data + + + + + /Bappa-dee-boopa-dee> + diff --git a/SafeExamBrowser.Core.UnitTests/I18n/Text_Valid.xml b/SafeExamBrowser.Core.UnitTests/I18n/Text_Valid.xml new file mode 100644 index 00000000..def6f21a --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/I18n/Text_Valid.xml @@ -0,0 +1,5 @@ + + + Application Log + Version + \ No newline at end of file diff --git a/SafeExamBrowser.Core.UnitTests/I18n/XmlTextResourceTests.cs b/SafeExamBrowser.Core.UnitTests/I18n/XmlTextResourceTests.cs new file mode 100644 index 00000000..ec24608c --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/I18n/XmlTextResourceTests.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.IO; +using System.Reflection; +using System.Xml; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Core.I18n; + +namespace SafeExamBrowser.Core.UnitTests.I18n +{ + [TestClass] + public class XmlTextResourceTests + { + [TestMethod] + public void MustCorrectlyLoadData() + { + var location = Assembly.GetAssembly(typeof(XmlTextResourceTests)).Location; + var path = Path.GetDirectoryName(location) + $@"\{nameof(I18n)}\Text_Valid.xml"; + var sut = new XmlTextResource(path); + + var text = sut.LoadText(); + + Assert.IsNotNull(text); + Assert.IsTrue(text.Count == 2); + Assert.AreEqual("Application Log", text[TextKey.LogWindow_Title]); + Assert.AreEqual("Version", text[TextKey.Version]); + } + + [TestMethod] + [ExpectedException(typeof(XmlException))] + public void MustFailWithInvalidData() + { + var location = Assembly.GetAssembly(typeof(XmlTextResourceTests)).Location; + var path = Path.GetDirectoryName(location) + $@"\{nameof(I18n)}\Text_Invalid.txt"; + var sut = new XmlTextResource(path); + + sut.LoadText(); + } + + [TestMethod] + public void MustNeverReturnNull() + { + var location = Assembly.GetAssembly(typeof(XmlTextResourceTests)).Location; + var path = Path.GetDirectoryName(location) + $@"\{nameof(I18n)}\Text_Incompatible.xml"; + var sut = new XmlTextResource(path); + + var text = sut.LoadText(); + + Assert.IsNotNull(text); + Assert.IsTrue(text.Count == 0); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void MustNotAcceptInvalidPath() + { + new XmlTextResource("This is not a valid path"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void MustNotAcceptNullAsPath() + { + new XmlTextResource(null); + } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index 4c3726c3..f0ab8304 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -69,12 +69,14 @@ ..\packages\Moq.4.7.63\lib\net45\Moq.dll + + @@ -91,6 +93,17 @@ + + + Always + + + Always + + + Always + + diff --git a/SafeExamBrowser.Core/Behaviour/Operations/I18nOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/I18nOperation.cs new file mode 100644 index 00000000..510c1101 --- /dev/null +++ b/SafeExamBrowser.Core/Behaviour/Operations/I18nOperation.cs @@ -0,0 +1,49 @@ +/* + * 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.Globalization; +using System.IO; +using System.Reflection; +using SafeExamBrowser.Contracts.Behaviour; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Core.I18n; + +namespace SafeExamBrowser.Core.Behaviour.Operations +{ + public class I18nOperation : IOperation + { + private ILogger logger; + private IText text; + + public ISplashScreen SplashScreen { private get; set; } + + public I18nOperation(ILogger logger, IText text) + { + this.logger = logger; + this.text = text; + } + + public void Perform() + { + logger.Info($"Loading default text data (the currently active culture is '{CultureInfo.CurrentCulture.Name}')..."); + + var location = Assembly.GetAssembly(typeof(XmlTextResource)).Location; + var path = Path.GetDirectoryName(location) + $@"\{nameof(I18n)}\Text.xml"; + var textResource = new XmlTextResource(path); + + text.Initialize(textResource); + } + + public void Revert() + { + // Nothing to do here... + } + } +} diff --git a/SafeExamBrowser.Core/Behaviour/RuntimeController.cs b/SafeExamBrowser.Core/Behaviour/RuntimeController.cs index fc4667ba..59913a1d 100644 --- a/SafeExamBrowser.Core/Behaviour/RuntimeController.cs +++ b/SafeExamBrowser.Core/Behaviour/RuntimeController.cs @@ -45,6 +45,7 @@ namespace SafeExamBrowser.Core.Behaviour public void Stop() { + displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged; processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted; windowMonitor.WindowChanged -= WindowMonitor_WindowChanged; } diff --git a/SafeExamBrowser.Core/I18n/Text.cs b/SafeExamBrowser.Core/I18n/Text.cs index aa88869d..9f46ae83 100644 --- a/SafeExamBrowser.Core/I18n/Text.cs +++ b/SafeExamBrowser.Core/I18n/Text.cs @@ -9,26 +9,40 @@ using System; using System.Collections.Generic; using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Core.I18n { public class Text : IText { - private readonly IDictionary cache; + private IDictionary cache = new Dictionary(); + private ILogger logger; - public Text(ITextResource resource) + public Text(ILogger logger) { - if (resource == null) - { - throw new ArgumentNullException(nameof(resource)); - } - - cache = resource.LoadText() ?? new Dictionary(); + this.logger = logger; } public string Get(TextKey key) { return cache.ContainsKey(key) ? cache[key] : $"Could not find string for key '{key}'!"; } + + public void Initialize(ITextResource resource) + { + if (resource == null) + { + throw new ArgumentNullException(nameof(resource)); + } + + try + { + cache = resource.LoadText() ?? new Dictionary(); + } + catch (Exception e) + { + logger.Error("Failed to load text data from provided resource!", e); + } + } } } diff --git a/SafeExamBrowser.Core/I18n/XmlTextResource.cs b/SafeExamBrowser.Core/I18n/XmlTextResource.cs index 236aa54b..75b8397f 100644 --- a/SafeExamBrowser.Core/I18n/XmlTextResource.cs +++ b/SafeExamBrowser.Core/I18n/XmlTextResource.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Xml.Linq; using SafeExamBrowser.Contracts.I18n; @@ -17,10 +16,30 @@ namespace SafeExamBrowser.Core.I18n { public class XmlTextResource : ITextResource { + private string path; + + /// + /// Initializes a new text resource for an XML file located at the specified path. + /// + /// If the specifed file does not exist. + /// If the given path is null. + public XmlTextResource(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (!File.Exists(path)) + { + throw new ArgumentException("The specified file does not exist!"); + } + + this.path = path; + } + public IDictionary LoadText() { - var assembly = Assembly.GetAssembly(typeof(XmlTextResource)).Location; - var path = Path.GetDirectoryName(assembly) + $@"\{nameof(I18n)}\Text.xml"; var xml = XDocument.Load(path); var text = new Dictionary(); diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index 8f5b78ec..47345186 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -55,6 +55,7 @@ + diff --git a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs index 4f87cfad..a9b3d59f 100644 --- a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs @@ -73,7 +73,7 @@ namespace SafeExamBrowser.UserInterface.Classic private void InitializeSplashScreen() { - InfoTextBlock.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic }); + InfoTextBlock.Inlines.Add(new Run($"Version {settings.ProgramVersion}") { FontStyle = FontStyles.Italic }); InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new Run(settings.ProgramCopyright) { FontSize = 10 }); diff --git a/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs index 87300276..591a5240 100644 --- a/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs @@ -73,7 +73,7 @@ namespace SafeExamBrowser.UserInterface.Windows10 private void InitializeSplashScreen() { - InfoTextBlock.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic }); + InfoTextBlock.Inlines.Add(new Run($"Version {settings.ProgramVersion}") { FontStyle = FontStyles.Italic }); InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new Run(settings.ProgramCopyright) { FontSize = 10 }); diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs index 80d4cafd..8c4c4c72 100644 --- a/SafeExamBrowser/CompositionRoot.cs +++ b/SafeExamBrowser/CompositionRoot.cs @@ -51,7 +51,6 @@ namespace SafeExamBrowser private ISystemComponent powerSupply; private ISystemInfo systemInfo; private IText text; - private ITextResource textResource; private IUserInterfaceFactory uiFactory; private IWindowMonitor windowMonitor; @@ -68,12 +67,11 @@ namespace SafeExamBrowser nativeMethods = new NativeMethods(); settings = new Settings(); systemInfo = new SystemInfo(); - textResource = new XmlTextResource(); uiFactory = new UserInterfaceFactory(); logger.Subscribe(new LogFileWriter(logFormatter, settings)); - text = new Text(textResource); + text = new Text(logger); Taskbar = new Taskbar(new ModuleLogger(logger, typeof(Taskbar))); browserController = new BrowserApplicationController(settings, text, uiFactory); displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods); @@ -89,6 +87,7 @@ namespace SafeExamBrowser StartupController = new StartupController(logger, settings, systemInfo, text, uiFactory); StartupOperations = new Queue(); + StartupOperations.Enqueue(new I18nOperation(logger, text)); StartupOperations.Enqueue(new KeyboardInterceptorOperation(keyboardInterceptor, logger, nativeMethods)); StartupOperations.Enqueue(new WindowMonitorOperation(logger, windowMonitor)); StartupOperations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));