SEBSP-26: Implemented capturing and transmission interval.
This commit is contained in:
parent
456894edb9
commit
cb81906945
8 changed files with 118 additions and 32 deletions
|
@ -288,7 +288,7 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private IOperation BuildProctoringOperation()
|
||||
{
|
||||
var controller = new ProctoringController(context.AppConfig, new FileSystem(), ModuleLogger(nameof(ProctoringController)), context.Server, text, uiFactory);
|
||||
var controller = new ProctoringController(context.AppConfig, new FileSystem(), ModuleLogger(nameof(ProctoringController)), nativeMethods, context.Server, text, uiFactory);
|
||||
var operation = new ProctoringOperation(actionCenter, context, controller, logger, taskbar, uiFactory);
|
||||
|
||||
return operation;
|
||||
|
|
|
@ -90,6 +90,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
|||
case Keys.Proctoring.ScreenProctoring.ClientSecret:
|
||||
MapClientSecret(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.Enabled:
|
||||
MapScreenProctoringEnabled(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.GroupId:
|
||||
MapGroupId(settings, value);
|
||||
break;
|
||||
|
@ -108,9 +111,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
|||
case Keys.Proctoring.ScreenProctoring.MinInterval:
|
||||
MapMinInterval(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.Enabled:
|
||||
MapScreenProctoringEnabled(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ServiceUrl:
|
||||
MapServiceUrl(settings, value);
|
||||
break;
|
||||
|
|
|
@ -19,6 +19,7 @@ using SafeExamBrowser.Server.Contracts.Events.Proctoring;
|
|||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Proctoring
|
||||
{
|
||||
|
@ -40,6 +41,7 @@ namespace SafeExamBrowser.Proctoring
|
|||
AppConfig appConfig,
|
||||
IFileSystem fileSystem,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IServerProxy server,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
|
@ -47,7 +49,7 @@ namespace SafeExamBrowser.Proctoring
|
|||
this.logger = logger;
|
||||
this.server = server;
|
||||
|
||||
factory = new ProctoringFactory(appConfig, fileSystem, logger, text, uiFactory);
|
||||
factory = new ProctoringFactory(appConfig, fileSystem, logger, nativeMethods, text, uiFactory);
|
||||
implementations = new List<ProctoringImplementation>();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
|||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Proctoring
|
||||
{
|
||||
|
@ -24,14 +25,22 @@ namespace SafeExamBrowser.Proctoring
|
|||
private readonly AppConfig appConfig;
|
||||
private readonly IFileSystem fileSystem;
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly IText text;
|
||||
private readonly IUserInterfaceFactory uiFactory;
|
||||
|
||||
public ProctoringFactory(AppConfig appConfig, IFileSystem fileSystem, IModuleLogger logger, IText text, IUserInterfaceFactory uiFactory)
|
||||
public ProctoringFactory(
|
||||
AppConfig appConfig,
|
||||
IFileSystem fileSystem,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.fileSystem = fileSystem;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
@ -52,7 +61,7 @@ namespace SafeExamBrowser.Proctoring
|
|||
var logger = this.logger.CloneFor(nameof(ScreenProctoring));
|
||||
var service = new ServiceProxy(logger.CloneFor(nameof(ServiceProxy)));
|
||||
|
||||
implementations.Add(new ScreenProctoringImplementation(logger, service, settings, text));
|
||||
implementations.Add(new ScreenProctoringImplementation(logger, nativeMethods, service, settings, text));
|
||||
}
|
||||
|
||||
return implementations;
|
||||
|
|
|
@ -145,6 +145,10 @@
|
|||
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
|
||||
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
|
||||
<Project>{7016F080-9AA5-41B2-A225-385AD877C171}</Project>
|
||||
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="JitsiMeet\index.html" />
|
||||
|
|
|
@ -46,12 +46,12 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Imaging
|
|||
|
||||
public string ToReducedString()
|
||||
{
|
||||
return $"{Width}x{Height}, {Data.Length / 1000:N0} kB, {Format.ToString().ToUpper()}";
|
||||
return $"{Width}x{Height}, {Data.Length / 1000:N0}kB, {Format.ToString().ToUpper()}";
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"resolution: {Width}x{Height}, size: {Data.Length / 1000:N0} kB, format: {Format.ToString().ToUpper()}";
|
||||
return $"resolution: {Width}x{Height}, size: {Data.Length / 1000:N0}kB, format: {Format.ToString().ToUpper()}";
|
||||
}
|
||||
|
||||
internal void Compress()
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.Core.Contracts.Notifications.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
|
@ -16,24 +19,42 @@ using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
|||
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
||||
using SafeExamBrowser.Server.Contracts.Events.Proctoring;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||
using MouseButton = SafeExamBrowser.WindowsApi.Contracts.Events.MouseButton;
|
||||
using MouseButtonState = SafeExamBrowser.WindowsApi.Contracts.Events.MouseButtonState;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
{
|
||||
internal class ScreenProctoringImplementation : ProctoringImplementation
|
||||
{
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly ServiceProxy service;
|
||||
private readonly ScreenProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
private readonly Timer timer;
|
||||
|
||||
private DateTime last;
|
||||
private Guid? keyboardHookId;
|
||||
private Guid? mouseHookId;
|
||||
|
||||
internal override string Name => nameof(ScreenProctoring);
|
||||
|
||||
public override event NotificationChangedEventHandler NotificationChanged;
|
||||
|
||||
internal ScreenProctoringImplementation(IModuleLogger logger, ServiceProxy service, ProctoringSettings settings, IText text)
|
||||
internal ScreenProctoringImplementation(
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
ServiceProxy service,
|
||||
ProctoringSettings settings,
|
||||
IText text)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.service = service;
|
||||
this.settings = settings.ScreenProctoring;
|
||||
this.text = text;
|
||||
|
@ -62,7 +83,6 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
|||
else
|
||||
{
|
||||
ShowNotificationInactive();
|
||||
|
||||
logger.Info($"Initialized proctoring: Not all settings are valid or a server session is active, not starting automatically.");
|
||||
}
|
||||
}
|
||||
|
@ -96,9 +116,12 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
|||
logger.Info("Successfully processed instruction.");
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Start()
|
||||
{
|
||||
last = DateTime.Now;
|
||||
keyboardHookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback);
|
||||
mouseHookId = nativeMethods.RegisterMouseHook(MouseHookCallback);
|
||||
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Start();
|
||||
|
||||
|
@ -109,6 +132,19 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
|||
|
||||
internal override void Stop()
|
||||
{
|
||||
if (keyboardHookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterKeyboardHook(keyboardHookId.Value);
|
||||
}
|
||||
|
||||
if (mouseHookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterMouseHook(mouseHookId.Value);
|
||||
}
|
||||
|
||||
keyboardHookId = default;
|
||||
mouseHookId = default;
|
||||
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
timer.Stop();
|
||||
|
||||
|
@ -120,11 +156,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
|||
|
||||
internal override void Terminate()
|
||||
{
|
||||
if (timer.Enabled)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
Stop();
|
||||
TerminateNotification();
|
||||
|
||||
logger.Info("Terminated proctoring.");
|
||||
|
@ -160,9 +192,28 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
|||
}
|
||||
}
|
||||
|
||||
private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
|
||||
{
|
||||
var key = KeyInterop.KeyFromVirtualKey(keyCode);
|
||||
|
||||
TryExecute();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
|
||||
{
|
||||
var isTouch = info.IsTouch;
|
||||
|
||||
TryExecute();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ShowNotificationActive()
|
||||
{
|
||||
// TODO: Replace with actual icon!
|
||||
// TODO: Extend INotification with IsEnabled or CanActivate, as the screen proctoring notification does not have any action or window!
|
||||
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ProctoringNotification_Active.xaml") };
|
||||
Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip);
|
||||
NotificationChanged?.Invoke();
|
||||
|
@ -187,21 +238,41 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
|||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
|
||||
{
|
||||
screenShot.Take();
|
||||
screenShot.Compress();
|
||||
service.SendScreenShot(screenShot);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to process screen shot!", e);
|
||||
}
|
||||
TryExecute();
|
||||
}
|
||||
|
||||
timer.Start();
|
||||
private void TryExecute()
|
||||
{
|
||||
if (MinimumIntervalElapsed() && Monitor.TryEnter(@lock))
|
||||
{
|
||||
last = DateTime.Now;
|
||||
timer.Stop();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
|
||||
{
|
||||
screenShot.Take();
|
||||
screenShot.Compress();
|
||||
service.Send(screenShot);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to process screen shot!", e);
|
||||
}
|
||||
});
|
||||
|
||||
timer.Start();
|
||||
Monitor.Exit(@lock);
|
||||
}
|
||||
}
|
||||
|
||||
private bool MinimumIntervalElapsed()
|
||||
{
|
||||
return DateTime.Now.Subtract(last) >= new TimeSpan(0, 0, 0, 0, settings.MinInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
|
|||
return new ServiceResponse(success, message);
|
||||
}
|
||||
|
||||
internal ServiceResponse SendScreenShot(ScreenShot screenShot)
|
||||
internal ServiceResponse Send(ScreenShot screenShot)
|
||||
{
|
||||
var request = new ScreenShotRequest(api, httpClient, logger, parser);
|
||||
var success = request.TryExecute(screenShot, SessionId, out var message);
|
||||
|
|
Loading…
Reference in a new issue