diff --git a/SafeExamBrowser.Configuration/Settings/SettingsRepository.cs b/SafeExamBrowser.Configuration/Settings/SettingsRepository.cs index f613eccc..596a24e0 100644 --- a/SafeExamBrowser.Configuration/Settings/SettingsRepository.cs +++ b/SafeExamBrowser.Configuration/Settings/SettingsRepository.cs @@ -13,19 +13,22 @@ namespace SafeExamBrowser.Configuration.Settings { public class SettingsRepository : ISettingsRepository { + public ISettings Current { get; private set; } + public ISettings Load(Uri path) { // TODO + return LoadDefaults(); } public ISettings LoadDefaults() { - var settings = new Settings(); + Current = new Settings(); // TODO - return settings; + return Current; } } } diff --git a/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs b/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs new file mode 100644 index 00000000..386af226 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs @@ -0,0 +1,39 @@ +/* + * 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.ServiceModel; +using SafeExamBrowser.Contracts.Communication.Messages; +using SafeExamBrowser.Contracts.Communication.Responses; + +namespace SafeExamBrowser.Contracts.Communication +{ + [ServiceContract(SessionMode = SessionMode.Required)] + public interface ICommunicationHost + { + /// + /// Initiates a connection to the host and must thus be called before any other opertion. To authenticate itself to the host, the + /// client can specify a security token. If the connection request was successful, a new session will be created by the host and + /// the client will subsequently be allowed to start communicating with the host. + /// + [OperationContract(IsInitiating = true)] + IConnectResponse Connect(Guid? token = null); + + /// + /// Closes the connection to the host and instructs it to terminate the communication session. + /// + [OperationContract(IsInitiating = false, IsTerminating = true)] + void Disconnect(IMessage message); + + /// + /// Sends a message to the host, optionally returning a response. If no response is expected, null will be returned. + /// + [OperationContract(IsInitiating = false)] + IResponse Send(IMessage message); + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs b/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs new file mode 100644 index 00000000..52d1a51e --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs @@ -0,0 +1,21 @@ +/* + * 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.Runtime.Serialization; + +namespace SafeExamBrowser.Contracts.Communication.Messages +{ + public interface IMessage : ISerializable + { + /// + /// The communication token needed for authentication with the host. + /// + Guid CommunicationToken { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Responses/IConnectResponse.cs b/SafeExamBrowser.Contracts/Communication/Responses/IConnectResponse.cs new file mode 100644 index 00000000..664973a7 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Responses/IConnectResponse.cs @@ -0,0 +1,25 @@ +/* + * 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; + +namespace SafeExamBrowser.Contracts.Communication.Responses +{ + public interface IConnectResponse : IResponse + { + /// + /// The communication token needed for authentication with the host. Is null if a connection was refused. + /// + Guid? CommunicationToken { get; } + + /// + /// Indicates whether the host has accepted the connection request. + /// + bool ConnectionEstablished { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Responses/IResponse.cs b/SafeExamBrowser.Contracts/Communication/Responses/IResponse.cs new file mode 100644 index 00000000..51562212 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Responses/IResponse.cs @@ -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/. + */ + +using System.Runtime.Serialization; + +namespace SafeExamBrowser.Contracts.Communication.Responses +{ + public interface IResponse : ISerializable + { + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/Settings/ISettingsRepository.cs b/SafeExamBrowser.Contracts/Configuration/Settings/ISettingsRepository.cs index 15fb434a..8c419131 100644 --- a/SafeExamBrowser.Contracts/Configuration/Settings/ISettingsRepository.cs +++ b/SafeExamBrowser.Contracts/Configuration/Settings/ISettingsRepository.cs @@ -12,6 +12,12 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings { public interface ISettingsRepository { + /// + /// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will + /// be null. + /// + ISettings Current { get; } + /// /// Attempts to load settings from the specified path. /// diff --git a/SafeExamBrowser.Contracts/Logging/ILogger.cs b/SafeExamBrowser.Contracts/Logging/ILogger.cs index 04cd8acc..c0351bae 100644 --- a/SafeExamBrowser.Contracts/Logging/ILogger.cs +++ b/SafeExamBrowser.Contracts/Logging/ILogger.cs @@ -13,6 +13,12 @@ namespace SafeExamBrowser.Contracts.Logging { public interface ILogger { + /// + /// Logs the given message with severity DEBUG. + /// + /// + void Debug(string message); + /// /// Logs the given message with severity INFO. /// diff --git a/SafeExamBrowser.Contracts/Logging/LogLevel.cs b/SafeExamBrowser.Contracts/Logging/LogLevel.cs index 81a845cd..37836a99 100644 --- a/SafeExamBrowser.Contracts/Logging/LogLevel.cs +++ b/SafeExamBrowser.Contracts/Logging/LogLevel.cs @@ -13,8 +13,9 @@ namespace SafeExamBrowser.Contracts.Logging /// public enum LogLevel { - Info = 1, - Warning = 2, - Error = 3 + Debug = 1, + Info = 2, + Warning = 3, + Error = 4 } } diff --git a/SafeExamBrowser.Contracts/Runtime/IRuntimeController.cs b/SafeExamBrowser.Contracts/Runtime/IRuntimeController.cs index 770fe573..c6452974 100644 --- a/SafeExamBrowser.Contracts/Runtime/IRuntimeController.cs +++ b/SafeExamBrowser.Contracts/Runtime/IRuntimeController.cs @@ -6,17 +6,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Configuration.Settings; - namespace SafeExamBrowser.Contracts.Runtime { public interface IRuntimeController { - /// - /// Allows to specify the application settings to be used during runtime. - /// - ISettings Settings { set; } - /// /// Wires up and starts the application event handling. /// diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index bfcd66da..3b391f27 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -50,9 +50,14 @@ + + + + + diff --git a/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs b/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs index 36f90238..e0bc98d2 100644 --- a/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Logging/LoggerTests.cs @@ -22,6 +22,7 @@ namespace SafeExamBrowser.Core.UnitTests.Logging public void MustAddMessagesToLog() { var sut = new Logger(); + var debug = "I'm a debug message"; var info = "I'm an info message"; var warn = "I'm a warning!"; var error = "I AM AN ERROR!!"; @@ -30,6 +31,7 @@ namespace SafeExamBrowser.Core.UnitTests.Logging var message = "I'm a simple text message"; var content = new LogText("I'm some raw log text..."); + sut.Debug(debug); sut.Info(info); sut.Warn(warn); sut.Error(error); @@ -39,34 +41,39 @@ namespace SafeExamBrowser.Core.UnitTests.Logging var log = sut.GetLog(); - Assert.IsTrue(log.Count == 7); + Assert.IsTrue(log.Count == 8); - Assert.IsTrue(info.Equals((log[0] as ILogMessage).Message)); - Assert.IsTrue((log[0] as ILogMessage).Severity == LogLevel.Info); + Assert.IsTrue(debug.Equals((log[0] as ILogMessage).Message)); + Assert.IsTrue((log[0] as ILogMessage).Severity == LogLevel.Debug); - Assert.IsTrue(warn.Equals((log[1] as ILogMessage).Message)); - Assert.IsTrue((log[1] as ILogMessage).Severity == LogLevel.Warning); + Assert.IsTrue(info.Equals((log[1] as ILogMessage).Message)); + Assert.IsTrue((log[1] as ILogMessage).Severity == LogLevel.Info); - Assert.IsTrue(error.Equals((log[2] as ILogMessage).Message)); - Assert.IsTrue((log[2] as ILogMessage).Severity == LogLevel.Error); + Assert.IsTrue(warn.Equals((log[2] as ILogMessage).Message)); + Assert.IsTrue((log[2] as ILogMessage).Severity == LogLevel.Warning); Assert.IsTrue(error.Equals((log[3] as ILogMessage).Message)); Assert.IsTrue((log[3] as ILogMessage).Severity == LogLevel.Error); - Assert.IsTrue((log[4] as ILogText).Text.Contains(exceptionMessage)); - Assert.IsTrue(message.Equals((log[5] as ILogText).Text)); + Assert.IsTrue(error.Equals((log[4] as ILogMessage).Message)); + Assert.IsTrue((log[4] as ILogMessage).Severity == LogLevel.Error); + Assert.IsTrue((log[5] as ILogText).Text.Contains(exceptionMessage)); - Assert.IsTrue(content.Text.Equals((log[6] as ILogText).Text)); + Assert.IsTrue(message.Equals((log[6] as ILogText).Text)); + + Assert.IsTrue(content.Text.Equals((log[7] as ILogText).Text)); } [TestMethod] public void MustReturnCopyOfLog() { var sut = new Logger(); + var debug = "I'm a debug message"; var info = "I'm an info message"; var warn = "I'm a warning!"; var error = "I AM AN ERROR!!"; + sut.Debug(debug); sut.Info(info); sut.Warn(warn); sut.Error(error); @@ -87,6 +94,7 @@ namespace SafeExamBrowser.Core.UnitTests.Logging { var sut = new Logger(); + Assert.ThrowsException(() => sut.Debug(null)); Assert.ThrowsException(() => sut.Info(null)); Assert.ThrowsException(() => sut.Warn(null)); Assert.ThrowsException(() => sut.Error(null)); diff --git a/SafeExamBrowser.Core/Communication/CommunicationHostProxy.cs b/SafeExamBrowser.Core/Communication/CommunicationHostProxy.cs new file mode 100644 index 00000000..c2739109 --- /dev/null +++ b/SafeExamBrowser.Core/Communication/CommunicationHostProxy.cs @@ -0,0 +1,106 @@ +/* + * 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.ServiceModel; +using SafeExamBrowser.Contracts.Communication; +using SafeExamBrowser.Contracts.Communication.Messages; +using SafeExamBrowser.Contracts.Communication.Responses; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Core.Communication +{ + public class CommunicationHostProxy : ICommunicationHost + { + private string address; + private ILogger logger; + private ICommunicationHost channel; + + public CommunicationHostProxy(ILogger logger, string address) + { + this.address = address; + this.logger = logger; + } + + public IConnectResponse Connect(Guid? token = null) + { + var endpoint = new EndpointAddress(address); + + channel = ChannelFactory.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint); + (channel as ICommunicationObject).Closed += CommunicationHostProxy_Closed; + (channel as ICommunicationObject).Closing += CommunicationHostProxy_Closing; + (channel as ICommunicationObject).Faulted += CommunicationHostProxy_Faulted; + + var response = channel.Connect(token); + + logger.Debug($"Tried to connect to {address}, connection was {(response.ConnectionEstablished ? "established" : "refused")}."); + + return response; + } + + public void Disconnect(IMessage message) + { + if (ChannelIsReady()) + { + channel.Disconnect(message); + logger.Debug($"Disconnected from {address}, transmitting {ToString(message)}."); + } + + throw new CommunicationException($"Tried to disconnect from host, but channel was {GetChannelState()}!"); + } + + public IResponse Send(IMessage message) + { + if (ChannelIsReady()) + { + var response = channel.Send(message); + + logger.Debug($"Sent {ToString(message)}, got {ToString(response)}."); + + return response; + } + + throw new CommunicationException($"Tried to send {ToString(message)}, but channel was {GetChannelState()}!"); + } + + private bool ChannelIsReady() + { + return channel != null && (channel as ICommunicationObject).State == CommunicationState.Opened; + } + + private void CommunicationHostProxy_Closed(object sender, EventArgs e) + { + logger.Debug("Communication channel has been closed."); + } + + private void CommunicationHostProxy_Closing(object sender, EventArgs e) + { + logger.Debug("Communication channel is closing."); + } + + private void CommunicationHostProxy_Faulted(object sender, EventArgs e) + { + logger.Error("Communication channel has faulted!"); + } + + private string GetChannelState() + { + return channel == null ? "null" : $"in state '{(channel as ICommunicationObject).State}'"; + } + + private string ToString(IMessage message) + { + return message != null ? $"message of type '{message.GetType()}'" : "no message"; + } + + private string ToString(IResponse response) + { + return response != null ? $"response of type '{response.GetType()}'" : "no response"; + } + } +} diff --git a/SafeExamBrowser.Core/Logging/Logger.cs b/SafeExamBrowser.Core/Logging/Logger.cs index e2a327f0..c5c4aaa6 100644 --- a/SafeExamBrowser.Core/Logging/Logger.cs +++ b/SafeExamBrowser.Core/Logging/Logger.cs @@ -21,6 +21,16 @@ namespace SafeExamBrowser.Core.Logging private readonly IList log = new List(); private readonly IList observers = new List(); + public void Debug(string message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + Add(LogLevel.Debug, message); + } + public void Info(string message) { if (message == null) diff --git a/SafeExamBrowser.Core/Logging/ModuleLogger.cs b/SafeExamBrowser.Core/Logging/ModuleLogger.cs index 040c9809..7370cf7c 100644 --- a/SafeExamBrowser.Core/Logging/ModuleLogger.cs +++ b/SafeExamBrowser.Core/Logging/ModuleLogger.cs @@ -27,6 +27,11 @@ namespace SafeExamBrowser.Core.Logging this.module = module; } + public void Debug(string message) + { + logger.Debug(AppendModuleInfo(message)); + } + public void Error(string message) { logger.Error(AppendModuleInfo(message)); diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index f0b0fcc2..b5f686b1 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -50,6 +50,7 @@ + @@ -57,6 +58,7 @@ + diff --git a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs index bdeab170..eb4b89ea 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs @@ -14,7 +14,6 @@ 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; using SafeExamBrowser.Runtime.Behaviour.Operations; @@ -24,7 +23,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations public class ConfigurationOperationTests { private Mock logger; - private Mock controller; private Mock info; private Mock repository; private Mock settings; @@ -37,7 +35,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations public void Initialize() { logger = new Mock(); - controller = new Mock(); info = new Mock(); repository = new Mock(); settings = new Mock(); @@ -55,23 +52,23 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations [TestMethod] public void MustNotFailWithoutCommandLineArgs() { - controller.SetupSet(c => c.Settings = It.IsAny()); + repository.Setup(r => r.LoadDefaults()); - sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) + sut = new ConfigurationOperation(logger.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, text.Object, uiFactory.Object, new string[] { }) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new string[] { }) { SplashScreen = splashScreen.Object }; sut.Perform(); - controller.VerifySet(c => c.Settings = It.IsAny(), Times.Exactly(2)); + repository.Verify(r => r.LoadDefaults(), Times.Exactly(2)); } [TestMethod] @@ -79,7 +76,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations { var path = @"an/invalid\path.'*%yolo/()"; - sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new [] { "blubb.exe", path }) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new [] { "blubb.exe", path }) { SplashScreen = splashScreen.Object }; @@ -96,14 +93,13 @@ 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, text.Object, uiFactory.Object, new[] { "blubb.exe", path }) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", path }) { SplashScreen = splashScreen.Object }; sut.Perform(); - controller.VerifySet(c => c.Settings = It.IsAny(), Times.Once); repository.Verify(r => r.Load(It.Is(u => u.Equals(new Uri(path)))), Times.Once); } @@ -115,14 +111,13 @@ 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, text.Object, uiFactory.Object, null) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) { SplashScreen = splashScreen.Object }; sut.Perform(); - controller.VerifySet(c => c.Settings = It.IsAny(), Times.Once); repository.Verify(r => r.Load(It.Is(u => u.Equals(new Uri(Path.Combine(location, "SettingsDummy.txt"))))), Times.Once); } @@ -133,28 +128,26 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations info.SetupGet(r => r.AppDataFolder).Returns(location); - sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) { SplashScreen = splashScreen.Object }; sut.Perform(); - controller.VerifySet(c => c.Settings = It.IsAny(), Times.Once); repository.Verify(r => r.Load(It.Is(u => u.Equals(new Uri(Path.Combine(location, "SettingsDummy.txt"))))), Times.Once); } [TestMethod] public void MustFallbackToDefaultsAsLastPrio() { - sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) { SplashScreen = splashScreen.Object }; sut.Perform(); - controller.VerifySet(c => c.Settings = It.IsAny(), Times.Once); repository.Verify(r => r.LoadDefaults(), Times.Once); } @@ -166,7 +159,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations info.SetupGet(r => r.ProgramDataFolder).Returns(location); uiFactory.Setup(u => u.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.Yes); - sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) { SplashScreen = splashScreen.Object }; @@ -181,7 +174,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations { uiFactory.Setup(u => u.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.No); - sut = new ConfigurationOperation(logger.Object, controller.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) + sut = new ConfigurationOperation(logger.Object, info.Object, repository.Object, text.Object, uiFactory.Object, null) { SplashScreen = splashScreen.Object }; diff --git a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceOperationTests.cs new file mode 100644 index 00000000..10d4df4b --- /dev/null +++ b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceOperationTests.cs @@ -0,0 +1,22 @@ +/* + * 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 Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations +{ + [TestClass] + public class ServiceOperationTests + { + [TestMethod] + public void Test() + { + // TODO + } + } +} diff --git a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj index 881e3ab3..e3b1a600 100644 --- a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj +++ b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj @@ -82,6 +82,7 @@ + diff --git a/SafeExamBrowser.Runtime/App.cs b/SafeExamBrowser.Runtime/App.cs index e1ff1797..f7118da0 100644 --- a/SafeExamBrowser.Runtime/App.cs +++ b/SafeExamBrowser.Runtime/App.cs @@ -60,7 +60,7 @@ namespace SafeExamBrowser.Runtime base.OnStartup(e); instances.BuildObjectGraph(); - LogStartupInformation(); + instances.LogStartupInformation(); var success = instances.StartupController.TryInitializeApplication(instances.StartupOperations); @@ -79,27 +79,11 @@ 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")}"); + instances.LogShutdownInformation(); 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(instances.StartupOperations.Reverse()); diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs index 85a8401b..90c92195 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs @@ -13,7 +13,6 @@ 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; namespace SafeExamBrowser.Runtime.Behaviour.Operations @@ -21,7 +20,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations internal class ConfigurationOperation : IOperation { private ILogger logger; - private IRuntimeController controller; private IRuntimeInfo runtimeInfo; private ISettingsRepository repository; private IText text; @@ -33,7 +31,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations public ConfigurationOperation( ILogger logger, - IRuntimeController controller, IRuntimeInfo runtimeInfo, ISettingsRepository repository, IText text, @@ -41,7 +38,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations string[] commandLineArgs) { this.logger = logger; - this.controller = controller; this.commandLineArgs = commandLineArgs; this.repository = repository; this.runtimeInfo = runtimeInfo; @@ -62,11 +58,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations logger.Info($"Loading configuration from '{uri.AbsolutePath}'..."); settings = repository.Load(uri); - if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && Abort()) + if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && UserWantsToAbortStartup()) { AbortStartup = true; - - return; + logger.Info($"The user chose to {(AbortStartup ? "abort" : "continue")} the application startup after successful client configuration."); } } else @@ -74,8 +69,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations logger.Info("No valid settings file specified nor found in PROGRAMDATA or APPDATA - loading default settings..."); settings = repository.LoadDefaults(); } - - controller.Settings = settings; } public void Revert() @@ -116,23 +109,13 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations return isValidUri; } - private bool Abort() + private bool UserWantsToAbortStartup() { 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; + var abort = uiFactory.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question); - 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; + return abort == MessageBoxResult.Yes; } } } diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceOperation.cs new file mode 100644 index 00000000..531cc21f --- /dev/null +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceOperation.cs @@ -0,0 +1,46 @@ +/* + * 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.Behaviour; +using SafeExamBrowser.Contracts.Communication; +using SafeExamBrowser.Contracts.Configuration.Settings; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; + +namespace SafeExamBrowser.Runtime.Behaviour.Operations +{ + internal class ServiceOperation : IOperation + { + private ICommunicationHost serviceHost; + private ILogger logger; + private ISettingsRepository settingsRepository; + + public bool AbortStartup { get; private set; } + public ISplashScreen SplashScreen { private get; set; } + + public ServiceOperation(ICommunicationHost serviceHost, ILogger logger, ISettingsRepository settingsRepository) + { + this.serviceHost = serviceHost; + this.logger = logger; + this.settingsRepository = settingsRepository; + } + + public void Perform() + { + logger.Info("Initializing service connection..."); + // SplashScreen.UpdateText(...) + + // TODO + } + + public void Revert() + { + // TODO + } + } +} diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 2535dab5..02ed3b77 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -17,6 +17,7 @@ using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Core.Behaviour; using SafeExamBrowser.Core.Behaviour.Operations; +using SafeExamBrowser.Core.Communication; using SafeExamBrowser.Core.I18n; using SafeExamBrowser.Core.Logging; using SafeExamBrowser.Runtime.Behaviour; @@ -28,11 +29,12 @@ namespace SafeExamBrowser.Runtime { internal class CompositionRoot { - internal ILogger Logger { get; private set; } - internal RuntimeInfo RuntimeInfo { get; private set; } + private ILogger logger; + private RuntimeInfo runtimeInfo; + private ISystemInfo systemInfo; + internal IShutdownController ShutdownController { get; private set; } internal IStartupController StartupController { get; private set; } - internal ISystemInfo SystemInfo { get; private set; } internal Queue StartupOperations { get; private set; } internal void BuildObjectGraph() @@ -42,24 +44,45 @@ namespace SafeExamBrowser.Runtime var settingsRepository = new SettingsRepository(); var uiFactory = new UserInterfaceFactory(); - Logger = new Logger(); - RuntimeInfo = new RuntimeInfo(); - SystemInfo = new SystemInfo(); + logger = new Logger(); + runtimeInfo = new RuntimeInfo(); + systemInfo = new SystemInfo(); InitializeRuntimeInfo(); InitializeLogging(); - var text = new Text(Logger); - var runtimeController = new RuntimeController(new ModuleLogger(Logger, typeof(RuntimeController))); + var text = new Text(logger); + var runtimeController = new RuntimeController(new ModuleLogger(logger, typeof(RuntimeController))); + var serviceProxy = new CommunicationHostProxy(new ModuleLogger(logger, typeof(CommunicationHostProxy)), "net.pipe://localhost/safeexambrowser/service"); - ShutdownController = new ShutdownController(Logger, RuntimeInfo, text, uiFactory); - StartupController = new StartupController(Logger, RuntimeInfo, SystemInfo, text, uiFactory); + ShutdownController = new ShutdownController(logger, runtimeInfo, text, uiFactory); + StartupController = new StartupController(logger, runtimeInfo, systemInfo, text, uiFactory); StartupOperations = new Queue(); - 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, runtimeInfo, settingsRepository, text, uiFactory, args)); + StartupOperations.Enqueue(new ServiceOperation(serviceProxy, logger, settingsRepository)); //StartupOperations.Enqueue(new KioskModeOperation()); - StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, Logger)); + StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, logger)); + } + + internal void LogStartupInformation() + { + var titleLine = $"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}{Environment.NewLine}"; + var copyrightLine = $"/* {runtimeInfo.ProgramCopyright}{Environment.NewLine}"; + var emptyLine = $"/* {Environment.NewLine}"; + var githubLine = $"/* Please visit https://www.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 {systemInfo.OperatingSystemInfo}"); + logger.Log(string.Empty); + } + + internal void LogShutdownInformation() + { + logger?.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } private void InitializeRuntimeInfo() @@ -70,25 +93,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().Copyright; - RuntimeInfo.ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser)); - RuntimeInfo.ProgramTitle = executable.GetCustomAttribute().Title; - RuntimeInfo.ProgramVersion = executable.GetCustomAttribute().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().Copyright; + runtimeInfo.ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser)); + runtimeInfo.ProgramTitle = executable.GetCustomAttribute().Title; + runtimeInfo.ProgramVersion = executable.GetCustomAttribute().InformationalVersion; + runtimeInfo.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.txt"); } 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); } } } diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index 2f587880..2b04d1f4 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -89,6 +89,7 @@ + Code