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:
parent
96bad137e5
commit
ccf7727d4c
23 changed files with 581 additions and 193 deletions
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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" />
|
||||
|
|
26
SafeExamBrowser.Contracts/Service/IServiceController.cs
Normal file
26
SafeExamBrowser.Contracts/Service/IServiceController.cs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
25
SafeExamBrowser.Service/ServiceController.cs
Normal file
25
SafeExamBrowser.Service/ServiceController.cs
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue