Implemented very basic log window.

This commit is contained in:
Damian Büchel 2017-08-07 12:23:56 +02:00
parent be49058e8c
commit e959c8cb39
22 changed files with 401 additions and 37 deletions

View file

@ -28,6 +28,8 @@ namespace SafeExamBrowser.Configuration
Mouse = new MouseSettings();
}
public bool AllowApplicationLog => true;
public string AppDataFolderName => "SafeExamBrowser";
public string ApplicationLogFile

View file

@ -10,6 +10,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
{
public interface ISettings
{
/// <summary>
/// Determines whether the user may access the application log during runtime.
/// </summary>
bool AllowApplicationLog { get; }
/// <summary>
/// The name used for the application data folder.
/// </summary>

View file

@ -14,6 +14,7 @@ namespace SafeExamBrowser.Contracts.I18n
public enum TextKey
{
Browser_ShowDeveloperConsole,
LogWindow_Title,
MessageBox_ShutdownError,
MessageBox_ShutdownErrorTitle,
MessageBox_SingleInstance,
@ -21,6 +22,7 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_StartupError,
MessageBox_StartupErrorTitle,
Notification_AboutTooltip,
Notification_LogTooltip,
SplashScreen_InitializeBrowser,
SplashScreen_InitializeProcessMonitoring,
SplashScreen_InitializeTaskbar,

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Logging
{
public interface ILogContentFormatter
{
/// <summary>
/// Formats the given log content and returns it as a <c>string</c>.
/// </summary>
string Format(ILogContent content);
}
}

View file

@ -76,6 +76,7 @@
<Compile Include="I18n\IText.cs" />
<Compile Include="I18n\TextKey.cs" />
<Compile Include="Logging\ILogContent.cs" />
<Compile Include="Logging\ILogContentFormatter.cs" />
<Compile Include="Logging\ILogger.cs" />
<Compile Include="Logging\ILogMessage.cs" />
<Compile Include="Logging\ILogObserver.cs" />

View file

@ -9,6 +9,7 @@
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Contracts.UserInterface
{
@ -29,6 +30,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
IBrowserWindow CreateBrowserWindow(IBrowserControl control, IBrowserSettings settings);
/// <summary>
/// Creates a new log window which runs on its own thread.
/// </summary>
IWindow CreateLogWindow(ILogger logger, ILogContentFormatter formatter, IText text);
/// <summary>
/// Creates a taskbar notification, initialized with the given notification information.
/// </summary>

View file

@ -18,7 +18,8 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public class TaskbarOperation : IOperation
{
private ILogger logger;
private INotificationController aboutController;
private ILogContentFormatter formatter;
private INotificationController aboutController, logController;
private ITaskbar taskbar;
private IUserInterfaceFactory uiFactory;
private IText text;
@ -26,9 +27,16 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public ISplashScreen SplashScreen { private get; set; }
public TaskbarOperation(ILogger logger, ISettings settings, ITaskbar taskbar, IText text, IUserInterfaceFactory uiFactory)
public TaskbarOperation(
ILogger logger,
ILogContentFormatter formatter,
ISettings settings,
ITaskbar taskbar,
IText text,
IUserInterfaceFactory uiFactory)
{
this.logger = logger;
this.formatter = formatter;
this.settings = settings;
this.taskbar = taskbar;
this.text = text;
@ -40,6 +48,22 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
logger.Info("Initializing taskbar...");
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeTaskbar);
if (settings.AllowApplicationLog)
{
CreateLogNotification();
}
CreateAboutNotification();
}
public void Revert()
{
logController?.Terminate();
aboutController.Terminate();
}
private void CreateAboutNotification()
{
var aboutInfo = new AboutNotificationInfo(text);
var aboutNotification = uiFactory.CreateNotification(aboutInfo);
@ -49,9 +73,15 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
taskbar.AddNotification(aboutNotification);
}
public void Revert()
private void CreateLogNotification()
{
aboutController.Terminate();
var logInfo = new LogNotificationInfo(text);
var logNotification = uiFactory.CreateNotification(logInfo);
logController = new LogNotificationController(logger, formatter, text, uiFactory);
logController.RegisterNotification(logNotification);
taskbar.AddNotification(logNotification);
}
}
}

View file

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<Text>
<Browser_ShowDeveloperConsole>Open Console</Browser_ShowDeveloperConsole>
<LogWindow_Title>Application Log</LogWindow_Title>
<MessageBox_ShutdownError>An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...</MessageBox_ShutdownError>
<MessageBox_ShutdownErrorTitle>Shutdown Error</MessageBox_ShutdownErrorTitle>
<MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError>
<MessageBox_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle>
<Notification_AboutTooltip>About Safe Exam Browser</Notification_AboutTooltip>
<Notification_LogTooltip>Application Log</Notification_LogTooltip>
<SplashScreen_InitializeBrowser>Initializing browser</SplashScreen_InitializeBrowser>
<SplashScreen_InitializeProcessMonitoring>Initializing process monitoring</SplashScreen_InitializeProcessMonitoring>
<SplashScreen_InitializeTaskbar>Initializing taskbar</SplashScreen_InitializeTaskbar>

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Core.Logging
{
public class DefaultLogFormatter : ILogContentFormatter
{
public string Format(ILogContent content)
{
if (content is ILogText)
{
return (content as ILogText).Text;
}
if (content is ILogMessage)
{
return FormatLogMessage(content as ILogMessage);
}
throw new NotImplementedException($"The default formatter is not yet implemented for log content of type {content.GetType()}!");
}
private string FormatLogMessage(ILogMessage message)
{
var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
var severity = message.Severity.ToString().ToUpper();
var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}";
return $"{date} [{threadInfo}] - {severity}: {message.Message}";
}
}
}

View file

@ -17,51 +17,28 @@ namespace SafeExamBrowser.Core.Logging
{
private static readonly object @lock = new object();
private readonly string filePath;
private readonly ILogContentFormatter formatter;
public LogFileWriter(ISettings settings)
public LogFileWriter(ILogContentFormatter formatter, ISettings settings)
{
if (!Directory.Exists(settings.LogFolderPath))
{
Directory.CreateDirectory(settings.LogFolderPath);
}
filePath = settings.ApplicationLogFile;
this.filePath = settings.ApplicationLogFile;
this.formatter = formatter;
}
public void Notify(ILogContent content)
{
if (content is ILogText)
{
WriteLogText(content as ILogText);
}
if (content is ILogMessage)
{
WriteLogMessage(content as ILogMessage);
}
}
private void WriteLogText(ILogText text)
{
Write(text.Text);
}
private void WriteLogMessage(ILogMessage message)
{
var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
var severity = message.Severity.ToString().ToUpper();
var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}";
Write($"{date} [{threadInfo}] - {severity}: {message.Message}");
}
private void Write(string content)
{
lock (@lock)
{
var raw = formatter.Format(content);
using (var stream = new StreamWriter(filePath, true, Encoding.UTF8))
{
stream.WriteLine(content);
stream.WriteLine(raw);
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Notifications
{
public class LogNotificationController : INotificationController
{
private ITaskbarNotification notification;
private ILogger logger;
private ILogContentFormatter formatter;
private IText text;
private IUserInterfaceFactory uiFactory;
private IWindow window;
public LogNotificationController(ILogger logger, ILogContentFormatter formatter, IText text, IUserInterfaceFactory uiFactory)
{
this.logger = logger;
this.formatter = formatter;
this.text = text;
this.uiFactory = uiFactory;
}
public void RegisterNotification(ITaskbarNotification notification)
{
this.notification = notification;
notification.Clicked += Notification_Clicked;
}
public void Terminate()
{
window?.Close();
}
private void Notification_Clicked()
{
if (window == null)
{
window = uiFactory.CreateLogWindow(logger, formatter, text);
window.Closing += () => window = null;
window.Show();
}
else
{
window.BringToForeground();
}
}
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Core.Notifications
{
class LogNotificationIconResource : IIconResource
{
public Uri Uri => new Uri("pack://application:,,,/SafeExamBrowser.UserInterface;component/Images/LogNotification.ico");
public bool IsBitmapResource => true;
public bool IsXamlResource => false;
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
namespace SafeExamBrowser.Core.Notifications
{
class LogNotificationInfo : INotificationInfo
{
private IText text;
public string Tooltip => text.Get(TextKey.Notification_LogTooltip);
public IIconResource IconResource { get; } = new LogNotificationIconResource();
public LogNotificationInfo(IText text)
{
this.text = text;
}
}
}

View file

@ -69,6 +69,7 @@
<Compile Include="Behaviour\Operations\WorkingAreaOperation.cs" />
<Compile Include="Behaviour\ShutdownController.cs" />
<Compile Include="Behaviour\StartupController.cs" />
<Compile Include="Logging\DefaultLogFormatter.cs" />
<Compile Include="Logging\LogFileWriter.cs" />
<Compile Include="Logging\LogMessage.cs" />
<Compile Include="I18n\Text.cs" />
@ -80,6 +81,9 @@
<Compile Include="Notifications\AboutNotificationController.cs" />
<Compile Include="Notifications\AboutNotificationIconResource.cs" />
<Compile Include="Notifications\AboutNotificationInfo.cs" />
<Compile Include="Notifications\LogNotificationController.cs" />
<Compile Include="Notifications\LogNotificationIconResource.cs" />
<Compile Include="Notifications\LogNotificationInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -0,0 +1,18 @@
<Window x:Class="SafeExamBrowser.UserInterface.LogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface"
mc:Ignorable="d"
Title="{Binding Path=WindowTitle}" Height="500" Width="1000" MinHeight="350" MinWidth="350" WindowStartupLocation="CenterScreen"
Icon="./Images/LogNotification.ico">
<Window.Background>
<SolidColorBrush Color="Black" Opacity="0.8" />
</Window.Background>
<Grid>
<TextBox x:Name="LogContent" AcceptsReturn="True" Background="Transparent" FontFamily="Consolas" Foreground="White"
HorizontalScrollBarVisibility="Auto" IsReadOnly="True" Text="{Binding Path=Text, Mode=OneWay}"
VerticalScrollBarVisibility="Auto" TextChanged="LogContent_TextChanged" />
</Grid>
</Window>

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Windows;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.ViewModels;
namespace SafeExamBrowser.UserInterface
{
public partial class LogWindow : Window, IWindow
{
private ILogger logger;
private LogViewModel model;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public LogWindow(ILogger logger, ILogContentFormatter formatter, IText text)
{
InitializeComponent();
this.logger = logger;
this.model = new LogViewModel(logger.GetLog(), formatter, text);
DataContext = model;
LogContent.DataContext = model;
logger.Subscribe(model);
}
public void BringToForeground()
{
Dispatcher.Invoke(Activate);
}
public new void Close()
{
Dispatcher.Invoke(() =>
{
logger.Unsubscribe(model);
base.Close();
});
}
public new void Show()
{
Dispatcher.Invoke(base.Show);
}
private void LogContent_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
LogContent.ScrollToEnd();
}
}
}

View file

@ -88,6 +88,9 @@
<Compile Include="Controls\QuitButton.xaml.cs">
<DependentUpon>QuitButton.xaml</DependentUpon>
</Compile>
<Compile Include="LogWindow.xaml.cs">
<DependentUpon>LogWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
@ -110,6 +113,7 @@
<Compile Include="UserInterfaceFactory.cs" />
<Compile Include="Utilities\IconResourceLoader.cs" />
<Compile Include="ViewModels\DateTimeViewModel.cs" />
<Compile Include="ViewModels\LogViewModel.cs" />
<Compile Include="ViewModels\SplashScreenViewModel.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
@ -149,6 +153,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="LogWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="SplashScreen.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -173,5 +181,8 @@
<ItemGroup>
<Resource Include="Images\Chromium.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="Images\LogNotification.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -11,6 +11,7 @@ using System.Windows;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.Controls;
@ -33,6 +34,31 @@ namespace SafeExamBrowser.UserInterface
return new BrowserWindow(control, settings);
}
public IWindow CreateLogWindow(ILogger logger, ILogContentFormatter formatter, IText text)
{
LogWindow logWindow = null;
var logWindowReadyEvent = new AutoResetEvent(false);
var logWindowThread = new Thread(() =>
{
logWindow = new LogWindow(logger, formatter, text);
logWindow.Closed += (o, args) => logWindow.Dispatcher.InvokeShutdown();
logWindow.Show();
logWindowReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run();
});
logWindowThread.SetApartmentState(ApartmentState.STA);
logWindowThread.Name = "Log Window Thread";
logWindowThread.IsBackground = true;
logWindowThread.Start();
logWindowReadyEvent.WaitOne();
return logWindow;
}
public ITaskbarNotification CreateNotification(INotificationInfo info)
{
return new NotificationIcon(info);

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.UserInterface.ViewModels
{
class LogViewModel : INotifyPropertyChanged, ILogObserver
{
private ILogContentFormatter formatter;
private IText text;
private StringBuilder builder = new StringBuilder();
public string Text
{
get { return builder.ToString(); }
}
public string WindowTitle => text.Get(TextKey.LogWindow_Title);
public event PropertyChangedEventHandler PropertyChanged;
public LogViewModel(IList<ILogContent> initial, ILogContentFormatter formatter, IText text)
{
this.formatter = formatter;
this.text = text;
foreach (var content in initial)
{
Notify(content);
}
}
public void Notify(ILogContent content)
{
builder.AppendLine(formatter.Format(content));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}

View file

@ -36,6 +36,7 @@ namespace SafeExamBrowser
private IApplicationInfo browserInfo;
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
private ILogContentFormatter logFormatter;
private IMouseInterceptor mouseInterceptor;
private INativeMethods nativeMethods;
private IProcessMonitor processMonitor;
@ -56,13 +57,14 @@ namespace SafeExamBrowser
{
browserInfo = new BrowserApplicationInfo();
logger = new Logger();
logFormatter = new DefaultLogFormatter();
nativeMethods = new NativeMethods();
settings = new SettingsImpl();
Taskbar = new Taskbar();
textResource = new XmlTextResource();
uiFactory = new UserInterfaceFactory();
logger.Subscribe(new LogFileWriter(settings));
logger.Subscribe(new LogFileWriter(logFormatter, settings));
text = new Text(textResource);
browserController = new BrowserApplicationController(settings, text, uiFactory);
@ -81,7 +83,7 @@ namespace SafeExamBrowser
StartupOperations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
StartupOperations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
StartupOperations.Enqueue(new WorkingAreaOperation(logger, Taskbar, workingArea));
StartupOperations.Enqueue(new TaskbarOperation(logger, settings, Taskbar, text, uiFactory));
StartupOperations.Enqueue(new TaskbarOperation(logger, logFormatter, settings, Taskbar, text, uiFactory));
StartupOperations.Enqueue(new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory));
StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, logger));
StartupOperations.Enqueue(new MouseInterceptorOperation(logger, mouseInterceptor, nativeMethods));