SEBWIN-301: Fixed usage of application data folder (local for large files vs. roaming for configuration) and implemented basic service operation for runtime.

This commit is contained in:
dbuechel 2019-06-06 15:44:03 +02:00
parent 96bad137e5
commit ccf7727d4c
23 changed files with 581 additions and 193 deletions

View file

@ -127,7 +127,7 @@ namespace SafeExamBrowser.Browser
var cefSettings = new CefSettings
{
CachePath = appConfig.BrowserCachePath,
LogFile = appConfig.BrowserLogFile,
LogFile = appConfig.BrowserLogFilePath,
LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info),
UserAgent = InitializeUserAgent()
};

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Communication.Proxies
public class ServiceProxy : BaseProxy, IServiceProxy
{
public bool Ignore { private get; set; }
public new bool IsConnected => base.IsConnected;
public ServiceProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger)
{

View file

@ -287,18 +287,18 @@ namespace SafeExamBrowser.Configuration.UnitTests
var appConfig = sut.InitializeAppConfig();
var clientAddress = appConfig.ClientAddress;
var clientId = appConfig.ClientId;
var clientLogFile = appConfig.ClientLogFile;
var clientLogFilePath = appConfig.ClientLogFilePath;
var runtimeAddress = appConfig.RuntimeAddress;
var runtimeId = appConfig.RuntimeId;
var runtimeLogFile = appConfig.RuntimeLogFile;
var runtimeLogFilePath = appConfig.RuntimeLogFilePath;
var configuration = sut.InitializeSessionConfiguration();
Assert.AreNotEqual(configuration.AppConfig.ClientAddress, clientAddress);
Assert.AreNotEqual(configuration.AppConfig.ClientId, clientId);
Assert.AreEqual(configuration.AppConfig.ClientLogFile, clientLogFile);
Assert.AreEqual(configuration.AppConfig.ClientLogFilePath, clientLogFilePath);
Assert.AreEqual(configuration.AppConfig.RuntimeAddress, runtimeAddress);
Assert.AreEqual(configuration.AppConfig.RuntimeId, runtimeId);
Assert.AreEqual(configuration.AppConfig.RuntimeLogFile, runtimeLogFile);
Assert.AreEqual(configuration.AppConfig.RuntimeLogFilePath, runtimeLogFilePath);
}
[TestMethod]

View file

@ -17,6 +17,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal class DataValues
{
private const string BASE_ADDRESS = "net.pipe://localhost/safeexambrowser";
private const string DEFAULT_FILE_NAME = "SebClientSettings.seb";
private AppConfig appConfig;
private string executablePath;
@ -34,35 +35,36 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal string GetAppDataFilePath()
{
return Path.Combine(appConfig.AppDataFolder, appConfig.DefaultSettingsFileName);
return appConfig.AppDataFilePath;
}
internal AppConfig InitializeAppConfig()
{
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
var appDataLocalFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), nameof(SafeExamBrowser));
var appDataRoamingFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
var programDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser));
var startTime = DateTime.Now;
var logFolder = Path.Combine(appDataFolder, "Logs");
var logFolder = Path.Combine(appDataLocalFolder, "Logs");
var logFilePrefix = startTime.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
appConfig = new AppConfig();
appConfig.AppDataFilePath = Path.Combine(appDataRoamingFolder, DEFAULT_FILE_NAME);
appConfig.ApplicationStartTime = startTime;
appConfig.AppDataFolder = appDataFolder;
appConfig.BrowserCachePath = Path.Combine(appDataFolder, "Cache");
appConfig.BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.log");
appConfig.BrowserCachePath = Path.Combine(appDataLocalFolder, "Cache");
appConfig.BrowserLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Browser.log");
appConfig.ClientId = Guid.NewGuid();
appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}";
appConfig.ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executablePath), $"{nameof(SafeExamBrowser)}.Client.exe");
appConfig.ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.log");
appConfig.ClientLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Client.log");
appConfig.ConfigurationFileExtension = ".seb";
appConfig.DefaultSettingsFileName = "SebClientSettings.seb";
appConfig.DownloadDirectory = Path.Combine(appDataFolder, "Downloads");
appConfig.DownloadDirectory = Path.Combine(appDataLocalFolder, "Downloads");
appConfig.ProgramCopyright = programCopyright;
appConfig.ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser));
appConfig.ProgramDataFilePath = Path.Combine(programDataFolder, DEFAULT_FILE_NAME);
appConfig.ProgramTitle = programTitle;
appConfig.ProgramVersion = programVersion;
appConfig.RuntimeId = Guid.NewGuid();
appConfig.RuntimeAddress = $"{BASE_ADDRESS}/runtime/{Guid.NewGuid()}";
appConfig.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.log");
appConfig.RuntimeLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.log");
appConfig.SebUriScheme = "seb";
appConfig.SebUriSchemeSecure = "sebs";
appConfig.ServiceAddress = $"{BASE_ADDRESS}/service";

View file

@ -32,7 +32,7 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
event CommunicationEventHandler ClientDisconnected;
/// <summary>
/// Event fired once the client has signaled that it is ready to operate.
/// Event fired when the client has signaled that it is ready to operate.
/// </summary>
event CommunicationEventHandler ClientReady;
@ -56,6 +56,26 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary>
event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
/// <summary>
/// Event fired when the service disconnected from the runtime.
/// </summary>
event CommunicationEventHandler ServiceDisconnected;
/// <summary>
/// Event fired when the service has experienced a critical failure.
/// </summary>
event CommunicationEventHandler ServiceFailed;
/// <summary>
/// Event fired when the service has successfully started a new session.
/// </summary>
event CommunicationEventHandler ServiceSessionStarted;
/// <summary>
/// Event fired when the service has successfully stopped the currently running session.
/// </summary>
event CommunicationEventHandler ServiceSessionStopped;
/// <summary>
/// Event fired when the client requests to shut down the application.
/// </summary>

View file

@ -22,6 +22,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// </summary>
bool Ignore { set; }
/// <summary>
/// Indicates whether a connection to the communication host of the service has been established.
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Instructs the service to start a new session according to the given parameters.
/// </summary>

View file

@ -17,9 +17,9 @@ namespace SafeExamBrowser.Contracts.Configuration
public class AppConfig
{
/// <summary>
/// The path of the application data folder.
/// The file path of the local client configuration for the active user.
/// </summary>
public string AppDataFolder { get; set; }
public string AppDataFilePath { get; set; }
/// <summary>
/// The point in time when the application was started.
@ -34,7 +34,7 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary>
/// The file path under which the log of the browser component is to be stored.
/// </summary>
public string BrowserLogFile { get; set; }
public string BrowserLogFilePath { get; set; }
/// <summary>
/// The communication address of the client component.
@ -54,18 +54,13 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary>
/// The file path under which the log of the client component is to be stored.
/// </summary>
public string ClientLogFile { get; set; }
public string ClientLogFilePath { get; set; }
/// <summary>
/// The file extension of configuration files for the application (including the period).
/// </summary>
public string ConfigurationFileExtension { get; set; }
/// <summary>
/// The default file name for application settings.
/// </summary>
public string DefaultSettingsFileName { get; set; }
/// <summary>
/// The default directory for file downloads.
/// </summary>
@ -77,9 +72,9 @@ namespace SafeExamBrowser.Contracts.Configuration
public string ProgramCopyright { get; set; }
/// <summary>
/// The path of the program data folder.
/// The file path of the local client configuration for all users.
/// </summary>
public string ProgramDataFolder { get; set; }
public string ProgramDataFilePath { get; set; }
/// <summary>
/// The program title of the application (i.e. the executing assembly).
@ -104,7 +99,7 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary>
/// The file path under which the log of the runtime component is to be stored.
/// </summary>
public string RuntimeLogFile { get; set; }
public string RuntimeLogFilePath { get; set; }
/// <summary>
/// The URI scheme for SEB resources.

View file

@ -144,6 +144,7 @@
<Compile Include="Applications\IApplicationInfo.cs" />
<Compile Include="Applications\IApplicationInstance.cs" />
<Compile Include="Client\INotificationInfo.cs" />
<Compile Include="Service\IServiceController.cs" />
<Compile Include="SystemComponents\ISystemInfo.cs" />
<Compile Include="SystemComponents\OperatingSystem.cs" />
<Compile Include="Configuration\Settings\BrowserSettings.cs" />

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2019 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.Service
{
/// <summary>
/// Controls the lifetime and is responsible for the event handling of the service application component.
/// </summary>
public interface IServiceController
{
/// <summary>
/// Reverts any changes and releases all resources used by the service.
/// </summary>
void Terminate();
/// <summary>
/// Tries to start the service. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool TryStart();
}
}

View file

@ -24,6 +24,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestClass]
public class ConfigurationOperationTests
{
private const string FILE_NAME = "SebClientSettings.seb";
private AppConfig appConfig;
private Mock<IHashAlgorithm> hashAlgorithm;
private Mock<ILogger> logger;
@ -43,9 +45,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
nextSession = new Mock<ISessionConfiguration>();
sessionContext = new SessionContext();
appConfig.AppDataFolder = @"C:\Not\Really\AppData";
appConfig.DefaultSettingsFileName = "SettingsDummy.txt";
appConfig.ProgramDataFolder = @"C:\Not\Really\ProgramData";
appConfig.AppDataFilePath = $@"C:\Not\Really\AppData\File.xml";
appConfig.ProgramDataFilePath = $@"C:\Not\Really\ProgramData\File.xml";
currentSession.SetupGet(s => s.AppConfig).Returns(appConfig);
nextSession.SetupGet(s => s.AppConfig).Returns(appConfig);
sessionContext.Current = currentSession.Object;
@ -57,11 +58,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var settings = new Settings { ConfigurationMode = ConfigurationMode.Exam };
var url = @"http://www.safeexambrowser.org/whatever.seb";
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
appConfig.AppDataFolder = location;
appConfig.ProgramDataFolder = location;
appConfig.DefaultSettingsFileName = "SettingsDummy.txt";
appConfig.AppDataFilePath = location;
appConfig.ProgramDataFilePath = location;
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
@ -76,36 +76,33 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void Perform_MustUseProgramDataAs2ndPrio()
{
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
var settings = default(Settings);
appConfig.ProgramDataFolder = location;
appConfig.AppDataFolder = $@"{location}\WRONG";
appConfig.ProgramDataFilePath = location;
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
var sut = new ConfigurationOperation(null, repository.Object, hashAlgorithm.Object, logger.Object, sessionContext);
var result = sut.Perform();
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(location)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Perform_MustUseAppDataAs3rdPrio()
{
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
var settings = default(Settings);
appConfig.AppDataFolder = location;
appConfig.AppDataFilePath = location;
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
var sut = new ConfigurationOperation(null, repository.Object, hashAlgorithm.Object, logger.Object, sessionContext);
var result = sut.Perform();
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(location)), out settings, It.IsAny<PasswordParameters>()), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
@ -284,10 +281,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var settings = new Settings { AdminPasswordHash = "9876", ConfigurationMode = ConfigurationMode.ConfigureClient };
var url = @"http://www.safeexambrowser.org/whatever.seb";
appConfig.AppDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
repository.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.LocalPath.Contains("SettingsDummy")), out localSettings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
repository.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.LocalPath.Contains(FILE_NAME)), out localSettings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
nextSession.SetupGet(s => s.Settings).Returns(settings);
var sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, hashAlgorithm.Object, logger.Object, sessionContext);
@ -341,7 +338,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var nextSettings = new Settings { AdminPasswordHash = "9876", ConfigurationMode = ConfigurationMode.ConfigureClient };
var url = @"http://www.safeexambrowser.org/whatever.seb";
appConfig.AppDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
nextSession.SetupGet(s => s.Settings).Returns(nextSettings);
hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is<string>(p => p == password))).Returns(currentSettings.AdminPasswordHash);
@ -373,7 +370,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var nextSettings = new Settings { AdminPasswordHash = "1234", ConfigurationMode = ConfigurationMode.ConfigureClient };
var url = @"http://www.safeexambrowser.org/whatever.seb";
appConfig.AppDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
nextSession.SetupGet(s => s.Settings).Returns(nextSettings);
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out currentSettings, It.IsAny<PasswordParameters>())).Returns(LoadStatus.Success);
@ -427,18 +424,16 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
public void Perform_MustUseCurrentPasswordIfAvailable()
{
var url = @"http://www.safeexambrowser.org/whatever.seb";
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
var appDataFile = new Uri(Path.Combine(location, "SettingsDummy.txt"));
var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
var settings = new Settings { AdminPasswordHash = "1234", ConfigurationMode = ConfigurationMode.Exam };
appConfig.AppDataFolder = location;
appConfig.DefaultSettingsFileName = "SettingsDummy.txt";
appConfig.AppDataFilePath = location;
repository
.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.IsAny<PasswordParameters>()))
.Returns(LoadStatus.PasswordNeeded);
repository
.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(appDataFile)), out settings, It.IsAny<PasswordParameters>()))
.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(new Uri(location))), out settings, It.IsAny<PasswordParameters>()))
.Returns(LoadStatus.Success);
repository
.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, It.Is<PasswordParameters>(p => p.IsHash == true && p.Password == settings.AdminPasswordHash)))
@ -460,7 +455,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var nextSettings = new Settings { AdminPasswordHash = "9876", ConfigurationMode = ConfigurationMode.ConfigureClient };
var url = @"http://www.safeexambrowser.org/whatever.seb";
appConfig.AppDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
appConfig.AppDataFilePath = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations), FILE_NAME);
nextSession.SetupGet(s => s.Settings).Returns(nextSettings);
hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is<string>(p => p == password))).Returns(currentSettings.AdminPasswordHash);
@ -510,7 +505,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var currentSettings = new Settings();
var location = Path.GetDirectoryName(GetType().Assembly.Location);
var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt"));
var resource = new Uri(Path.Combine(location, nameof(Operations), FILE_NAME));
var settings = new Settings { ConfigurationMode = ConfigurationMode.Exam };
currentSession.SetupGet(s => s.Settings).Returns(currentSettings);
@ -532,7 +527,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var currentSettings = new Settings();
var location = Path.GetDirectoryName(GetType().Assembly.Location);
var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt"));
var resource = new Uri(Path.Combine(location, nameof(Operations), FILE_NAME));
var settings = new Settings { ConfigurationMode = ConfigurationMode.ConfigureClient };
currentSession.SetupGet(s => s.Settings).Returns(currentSettings);
@ -577,7 +572,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var currentSettings = new Settings();
var location = Path.GetDirectoryName(GetType().Assembly.Location);
var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt"));
var resource = new Uri(Path.Combine(location, nameof(Operations), FILE_NAME));
var settings = new Settings { ConfigurationMode = ConfigurationMode.ConfigureClient };
currentSession.SetupGet(s => s.Settings).Returns(currentSettings);

View file

@ -9,6 +9,7 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
@ -22,6 +23,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
public class ServiceOperationTests
{
private Mock<ILogger> logger;
private Mock<IRuntimeHost> runtimeHost;
private Mock<IServiceProxy> service;
private Mock<ISessionConfiguration> session;
private SessionContext sessionContext;
@ -32,6 +34,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
public void Initialize()
{
logger = new Mock<ILogger>();
runtimeHost = new Mock<IRuntimeHost>();
service = new Mock<IServiceProxy>();
session = new Mock<ISessionConfiguration>();
sessionContext = new SessionContext();
@ -40,12 +43,13 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sessionContext.Current = session.Object;
sessionContext.Next = session.Object;
session.SetupGet(s => s.Settings).Returns(settings);
settings.ServicePolicy = ServicePolicy.Mandatory;
sut = new ServiceOperation(logger.Object, service.Object, sessionContext);
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, 0);
}
[TestMethod]
public void MustConnectToService()
public void Perform_MustConnectToService()
{
service.Setup(s => s.Connect(null, true)).Returns(true);
settings.ServicePolicy = ServicePolicy.Mandatory;
@ -61,18 +65,67 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
}
[TestMethod]
public void MustStartSessionIfConnected()
public void Perform_MustStartSessionIfConnected()
{
service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true);
service
.Setup(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null));
sut.Perform();
var result = sut.Perform();
service.Verify(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustNotStartSessionIfNotConnected()
public void Perform_MustFailIfSessionStartUnsuccessful()
{
service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true);
service
.Setup(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null));
var result = sut.Perform();
service.Verify(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
public void Perform_MustFailIfSessionNotStartedWithinTimeout()
{
const int TIMEOUT = 50;
var after = default(DateTime);
var before = default(DateTime);
service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true);
service.Setup(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>())).Returns(new CommunicationResult(true));
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT);
before = DateTime.Now;
var result = sut.Perform();
after = DateTime.Now;
service.Verify(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);
Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT));
}
[TestMethod]
public void Perform_MustNotStartSessionIfNotConnected()
{
service.SetupGet(s => s.IsConnected).Returns(false);
service.Setup(s => s.Connect(null, true)).Returns(false);
sut.Perform();
@ -81,22 +134,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
}
[TestMethod]
public void MustNotFailIfServiceNotAvailable()
{
service.Setup(s => s.Connect(null, true)).Returns(false);
settings.ServicePolicy = ServicePolicy.Mandatory;
sut.Perform();
service.Setup(s => s.Connect(null, true)).Returns(false);
settings.ServicePolicy = ServicePolicy.Optional;
sut.Perform();
}
[TestMethod]
public void MustFailIfServiceMandatoryAndNotAvailable()
public void Perform_MustFailIfServiceMandatoryAndNotAvailable()
{
service.SetupGet(s => s.IsConnected).Returns(false);
service.Setup(s => s.Connect(null, true)).Returns(false);
settings.ServicePolicy = ServicePolicy.Mandatory;
@ -106,87 +146,185 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
}
[TestMethod]
public void MustNotFailIfServiceOptionalAndNotAvailable()
public void Perform_MustNotFailIfServiceOptionalAndNotAvailable()
{
service.SetupGet(s => s.IsConnected).Returns(false);
service.Setup(s => s.Connect(null, true)).Returns(false);
settings.ServicePolicy = ServicePolicy.Optional;
var result = sut.Perform();
service.VerifySet(s => s.Ignore = true);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Repeat_MustStopCurrentAndStartNewSession()
{
service
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally();
var result = sut.Repeat();
service.Verify(s => s.Connect(It.IsAny<Guid?>(), It.IsAny<bool>()), Times.Once);
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()), Times.Exactly(2));
service.Verify(s => s.Disconnect(), Times.Never);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustDisconnectWhenReverting()
public void Repeat_MustFailIfCurrentSessionWasNotStoppedSuccessfully()
{
service.Setup(s => s.Connect(null, true)).Returns(true);
settings.ServicePolicy = ServicePolicy.Mandatory;
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(false));
sut.Perform();
sut.Revert();
PerformNormally();
service.Setup(s => s.Connect(null, true)).Returns(true);
settings.ServicePolicy = ServicePolicy.Optional;
sut.Perform();
sut.Revert();
service.Verify(s => s.Disconnect(), Times.Exactly(2));
}
[TestMethod]
public void MustStopSessionWhenReverting()
{
service.Setup(s => s.Connect(null, true)).Returns(true);
sut.Perform();
sut.Revert();
var result = sut.Repeat();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
public void MustNotStopSessionWhenRevertingAndNotConnected()
public void Revert_MustDisconnect()
{
service.Setup(s => s.Connect(null, true)).Returns(false);
service.Setup(s => s.Disconnect()).Returns(true).Callback(() => runtimeHost.Raise(h => h.ServiceDisconnected += null));
service
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
sut.Perform();
sut.Revert();
PerformNormally();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Never);
}
[TestMethod]
public void MustNotFailWhenDisconnecting()
{
service.Setup(s => s.Connect(null, true)).Returns(true);
service.Setup(s => s.Disconnect()).Returns(false);
settings.ServicePolicy = ServicePolicy.Optional;
sut.Perform();
sut.Revert();
var result = sut.Revert();
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustNotDisconnnectIfNotAvailable()
public void Revert_MustFailIfServiceNotDisconnectedWithinTimeout()
{
service.Setup(s => s.Connect(null, true)).Returns(false);
settings.ServicePolicy = ServicePolicy.Mandatory;
const int TIMEOUT = 50;
sut.Perform();
sut.Revert();
var after = default(DateTime);
var before = default(DateTime);
service.Setup(s => s.Connect(null, true)).Returns(false);
settings.ServicePolicy = ServicePolicy.Optional;
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT);
sut.Perform();
sut.Revert();
service.Setup(s => s.Disconnect()).Returns(true);
service
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally();
before = DateTime.Now;
var result = sut.Revert();
after = DateTime.Now;
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);
Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT));
}
[TestMethod]
public void Revert_MustStopSessionIfConnected()
{
service.Setup(s => s.Disconnect()).Returns(true).Callback(() => runtimeHost.Raise(h => h.ServiceDisconnected += null));
service
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally();
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Revert_MustHandleCommunicationFailureWhenStoppingSession()
{
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(false));
PerformNormally();
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
public void Revert_MustFailIfSessionNotStoppedWithinTimeout()
{
const int TIMEOUT = 50;
var after = default(DateTime);
var before = default(DateTime);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT);
PerformNormally();
before = DateTime.Now;
var result = sut.Revert();
after = DateTime.Now;
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);
Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT));
}
[TestMethod]
public void Revert_MustNotStopSessionWhenNotConnected()
{
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Never);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Revert_MustNotDisconnnectIfNotConnected()
{
var result = sut.Revert();
service.Verify(s => s.Disconnect(), Times.Never);
Assert.AreEqual(OperationResult.Success, result);
}
private void PerformNormally()
{
service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true);
service
.Setup(s => s.StartSession(It.IsAny<Guid>(), It.IsAny<Settings>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null));
sut.Perform();
}
}
}

View file

@ -121,12 +121,12 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Operations\SettingsDummy.txt">
<None Include="Operations\SebClientSettings.seb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Operations\WRONG\SettingsDummy.txt">
</None>
<None Include="Operations\WRONG\SebClientSettings.seb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</None>
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View file

@ -26,6 +26,10 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ServiceDisconnected;
public event CommunicationEventHandler ServiceFailed;
public event CommunicationEventHandler ServiceSessionStarted;
public event CommunicationEventHandler ServiceSessionStopped;
public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms)

View file

@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new HashAlgorithm(), logger, sessionContext));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(logger, serviceProxy, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
@ -170,7 +170,7 @@ namespace SafeExamBrowser.Runtime
private void InitializeLogging()
{
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), appConfig.RuntimeLogFile);
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), appConfig.RuntimeLogFilePath);
logFileWriter.Initialize();
logger.LogLevel = LogLevel.Debug;

View file

@ -96,7 +96,7 @@ namespace SafeExamBrowser.Runtime.Operations
private bool TryStartClient()
{
var clientExecutable = Context.Next.AppConfig.ClientExecutablePath;
var clientLogFile = $"{'"' + Context.Next.AppConfig.ClientLogFile + '"'}";
var clientLogFile = $"{'"' + Context.Next.AppConfig.ClientLogFilePath + '"'}";
var clientLogLevel = Context.Next.Settings.LogLevel.ToString();
var runtimeHostUri = Context.Next.AppConfig.RuntimeAddress;
var startupToken = Context.Next.StartupToken.ToString("D");

View file

@ -27,15 +27,8 @@ namespace SafeExamBrowser.Runtime.Operations
private IHashAlgorithm hashAlgorithm;
private ILogger logger;
private string AppDataFile
{
get { return Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); }
}
private string ProgramDataFile
{
get { return Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); }
}
private string AppDataFilePath => Context.Next.AppConfig.AppDataFilePath;
private string ProgramDataFilePath => Context.Next.AppConfig.ProgramDataFilePath;
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;
@ -119,16 +112,16 @@ namespace SafeExamBrowser.Runtime.Operations
if (source == UriSource.CommandLine)
{
var hasAppDataFile = File.Exists(AppDataFile);
var hasProgramDataFile = File.Exists(ProgramDataFile);
var hasAppDataFile = File.Exists(AppDataFilePath);
var hasProgramDataFile = File.Exists(ProgramDataFilePath);
if (hasProgramDataFile)
{
status = TryLoadSettings(new Uri(ProgramDataFile, UriKind.Absolute), UriSource.ProgramData, out _, out settings);
status = TryLoadSettings(new Uri(ProgramDataFilePath, UriKind.Absolute), UriSource.ProgramData, out _, out settings);
}
else if (hasAppDataFile)
{
status = TryLoadSettings(new Uri(AppDataFile, UriKind.Absolute), UriSource.AppData, out _, out settings);
status = TryLoadSettings(new Uri(AppDataFilePath, UriKind.Absolute), UriSource.AppData, out _, out settings);
}
if ((!hasProgramDataFile && !hasAppDataFile) || status == LoadStatus.Success)
@ -412,18 +405,18 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info($"Found command-line argument for configuration resource: '{uri}', the URI is {(isValidUri ? "valid" : "invalid")}.");
}
if (!isValidUri && File.Exists(ProgramDataFile))
if (!isValidUri && File.Exists(ProgramDataFilePath))
{
isValidUri = Uri.TryCreate(ProgramDataFile, UriKind.Absolute, out uri);
isValidUri = Uri.TryCreate(ProgramDataFilePath, UriKind.Absolute, out uri);
source = UriSource.ProgramData;
logger.Info($"Found configuration file in PROGRAMDATA directory: '{uri}'.");
logger.Info($"Found configuration file in program data directory: '{uri}'.");
}
if (!isValidUri && File.Exists(AppDataFile))
if (!isValidUri && File.Exists(AppDataFilePath))
{
isValidUri = Uri.TryCreate(AppDataFile, UriKind.Absolute, out uri);
isValidUri = Uri.TryCreate(AppDataFilePath, UriKind.Absolute, out uri);
source = UriSource.AppData;
logger.Info($"Found configuration file in APPDATA directory: '{uri}'.");
logger.Info($"Found configuration file in app data directory: '{uri}'.");
}
return isValidUri;

View file

@ -6,6 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Threading;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
@ -17,17 +20,25 @@ namespace SafeExamBrowser.Runtime.Operations
{
internal class ServiceOperation : SessionOperation
{
private bool connected, mandatory;
private ILogger logger;
private IRuntimeHost runtimeHost;
private IServiceProxy service;
private int timeout_ms;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ServiceOperation(ILogger logger, IServiceProxy service, SessionContext sessionContext) : base(sessionContext)
public ServiceOperation(
ILogger logger,
IRuntimeHost runtimeHost,
IServiceProxy service,
SessionContext sessionContext,
int timeout_ms) : base(sessionContext)
{
this.logger = logger;
this.runtimeHost = runtimeHost;
this.service = service;
this.timeout_ms = timeout_ms;
}
public override OperationResult Perform()
@ -35,37 +46,38 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info($"Initializing service session...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
mandatory = Context.Next.Settings.ServicePolicy == ServicePolicy.Mandatory;
connected = service.Connect();
var success = TryEstablishConnection();
if (mandatory && !connected)
if (service.IsConnected)
{
logger.Error("Failed to initialize a service session since the service is mandatory but not available!");
return OperationResult.Failed;
success = TryStartSession();
}
service.Ignore = !connected;
logger.Info($"The service is {(mandatory ? "mandatory" : "optional")} and {(connected ? "available." : "not available. All service-related operations will be ignored!")}");
if (connected)
{
StartServiceSession();
}
return OperationResult.Success;
return success ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Repeat()
{
var result = Revert();
logger.Info($"Initializing new service session...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
if (result != OperationResult.Success)
var success = false;
if (service.IsConnected)
{
return result;
success = TryStopSession();
}
else
{
success = TryEstablishConnection();
}
return Perform();
if (success && service.IsConnected)
{
success = TryStartSession();
}
return success ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Revert()
@ -73,33 +85,154 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info("Finalizing service session...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
if (connected)
{
StopServiceSession();
var success = true;
var success = service.Disconnect();
if (service.IsConnected)
{
success &= TryStopSession();
success &= TryTerminateConnection();
}
return success ? OperationResult.Success : OperationResult.Failed;
}
private bool TryEstablishConnection()
{
var mandatory = Context.Next.Settings.ServicePolicy == ServicePolicy.Mandatory;
var connected = service.Connect();
var success = connected || !mandatory;
if (success)
{
service.Ignore = !connected;
logger.Info($"The service is {(mandatory ? "mandatory" : "optional")} and {(connected ? "connected." : "not connected. All service-related operations will be ignored!")}");
}
else
{
logger.Error("The service is mandatory but no connection could be established!");
}
return success;
}
private bool TryTerminateConnection()
{
var serviceEvent = new AutoResetEvent(false);
var serviceEventHandler = new CommunicationEventHandler(() => serviceEvent.Set());
runtimeHost.ServiceDisconnected += serviceEventHandler;
var success = service.Disconnect();
if (success)
{
logger.Info("Successfully disconnected from service. Waiting for service to disconnect...");
success = serviceEvent.WaitOne(timeout_ms);
if (success)
{
logger.Info("Successfully disconnected from the service.");
logger.Info("Service disconnected successfully.");
}
else
{
logger.Error("Failed to disconnect from the service!");
logger.Error($"Service failed to disconnect within {timeout_ms / 1000} seconds!");
}
}
else
{
logger.Error("Failed to disconnect from service!");
}
return OperationResult.Success;
runtimeHost.ServiceDisconnected -= serviceEventHandler;
return success;
}
private void StartServiceSession()
private bool TryStartSession()
{
service.StartSession(Context.Next.Id, Context.Next.Settings);
var failure = false;
var success = false;
var serviceEvent = new AutoResetEvent(false);
var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); });
var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); });
runtimeHost.ServiceFailed += failureEventHandler;
runtimeHost.ServiceSessionStarted += successEventHandler;
logger.Info("Starting new service session...");
var communication = service.StartSession(Context.Next.Id, Context.Next.Settings);
if (communication.Success)
{
serviceEvent.WaitOne(timeout_ms);
if (success)
{
logger.Info("Successfully started new service session.");
}
else if (failure)
{
logger.Error("An error occurred while attempting to start a new service session! Please check the service log for further information.");
}
else
{
logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!");
}
}
else
{
logger.Error("Failed to communicate session start to service!");
}
runtimeHost.ServiceFailed -= failureEventHandler;
runtimeHost.ServiceSessionStarted -= successEventHandler;
return success;
}
private void StopServiceSession()
private bool TryStopSession()
{
service.StopSession(Context.Current.Id);
var failure = false;
var success = false;
var serviceEvent = new AutoResetEvent(false);
var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); });
var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); });
runtimeHost.ServiceFailed += failureEventHandler;
runtimeHost.ServiceSessionStopped += successEventHandler;
logger.Info("Stopping current service session...");
var communication = service.StopSession(Context.Current.Id);
if (communication.Success)
{
serviceEvent.WaitOne(timeout_ms);
if (success)
{
logger.Info("Successfully stopped service session.");
}
else if (failure)
{
logger.Error("An error occurred while attempting to stop the current service session! Please check the service log for further information.");
}
else
{
logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!");
}
}
else
{
logger.Error("Failed to communicate session stop to service!");
}
runtimeHost.ServiceFailed -= failureEventHandler;
runtimeHost.ServiceSessionStopped -= successEventHandler;
return success;
}
}
}

View file

@ -6,13 +6,51 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.IO;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Service;
using SafeExamBrowser.Logging;
namespace SafeExamBrowser.Service
{
internal class CompositionRoot
{
private ILogger logger;
internal IServiceController ServiceController { get; private set; }
internal void BuildObjectGraph()
{
logger = new Logger();
InitializeLogging();
ServiceController = new ServiceController();
}
internal void LogStartupInformation()
{
logger.Log($"# Service started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log(string.Empty);
}
internal void LogShutdownInformation()
{
logger?.Log($"# Service terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
private void InitializeLogging()
{
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), nameof(SafeExamBrowser));
var logFolder = Path.Combine(appDataFolder, "Logs");
var logFilePrefix = DateTime.Now.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
var logFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Service.log");
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath);
logFileWriter.Initialize();
logger.LogLevel = LogLevel.Debug;
logger.Subscribe(logFileWriter);
}
}
}

View file

@ -67,9 +67,20 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceController.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Contracts\SafeExamBrowser.Contracts.csproj">
<Project>{47da5933-bef8-4729-94e6-abde2db12262}</Project>
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging\SafeExamBrowser.Logging.csproj">
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
<Name>SafeExamBrowser.Logging</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.ServiceProcess;
namespace SafeExamBrowser.Service
@ -27,22 +28,22 @@ namespace SafeExamBrowser.Service
protected override void OnStart(string[] args)
{
//instances = new CompositionRoot();
//instances.BuildObjectGraph();
//instances.LogStartupInformation();
instances = new CompositionRoot();
instances.BuildObjectGraph();
instances.LogStartupInformation();
//var success = instances.ServiceController.TryStart();
var success = instances.ServiceController.TryStart();
//if (!success)
//{
// Environment.Exit(-1);
//}
if (!success)
{
Environment.Exit(-1);
}
}
protected override void OnStop()
{
//instances?.ServiceController?.Terminate();
//instances?.LogShutdownInformation();
instances?.ServiceController?.Terminate();
instances?.LogShutdownInformation();
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2019 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.Service;
namespace SafeExamBrowser.Service
{
internal class ServiceController : IServiceController
{
public bool TryStart()
{
return true;
}
public void Terminate()
{
}
}
}