/* * Copyright (c) 2024 ETH Zürich, IT Services * * 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.Browser.Contracts; using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Core.Contracts.Resources.Icons; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Monitoring.Contracts.Applications; using SafeExamBrowser.Proctoring.ScreenProctoring.Data; using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging; using SafeExamBrowser.Proctoring.ScreenProctoring.Service; using SafeExamBrowser.Server.Contracts.Events.Proctoring; using SafeExamBrowser.Settings.Proctoring; using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Proctoring.ScreenProctoring { internal class ScreenProctoringImplementation : ProctoringImplementation { private readonly DataCollector collector; private readonly IModuleLogger logger; private readonly ServiceProxy service; private readonly ScreenProctoringSettings settings; private readonly TransmissionSpooler spooler; private readonly IText text; internal override string Name => nameof(ScreenProctoring); internal ScreenProctoringImplementation( AppConfig appConfig, IApplicationMonitor applicationMonitor, IBrowserApplication browser, IModuleLogger logger, INativeMethods nativeMethods, ServiceProxy service, ProctoringSettings settings, IText text) { this.collector = new DataCollector(applicationMonitor, browser, logger.CloneFor(nameof(DataCollector)), nativeMethods, settings.ScreenProctoring); this.logger = logger; this.service = service; this.settings = settings.ScreenProctoring; this.spooler = new TransmissionSpooler(appConfig, logger.CloneFor(nameof(TransmissionSpooler)), service); this.text = text; } internal override void ExecuteRemainingWork() { logger.Info("Starting execution of remaining work..."); spooler.ExecuteRemainingWork(InvokeRemainingWorkUpdated); logger.Info("Terminated execution of remaining work."); } internal override bool HasRemainingWork() { var hasWork = spooler.HasRemainingWork(); if (hasWork) { logger.Info("There is remaining work to be done."); } else { logger.Info("There is no remaining work to be done."); } return hasWork; } internal override void Initialize() { var start = true; start &= !string.IsNullOrWhiteSpace(settings.ClientId); start &= !string.IsNullOrWhiteSpace(settings.ClientSecret); start &= !string.IsNullOrWhiteSpace(settings.GroupId); start &= !string.IsNullOrWhiteSpace(settings.ServiceUrl); if (start) { logger.Info($"Initialized proctoring: All settings are valid, starting automatically..."); Connect(); Start(); } else { UpdateNotification(false); logger.Info($"Initialized proctoring: Not all settings are valid or a server session is active, not starting automatically."); } } internal override void ProctoringConfigurationReceived(bool allowChat, bool receiveAudio, bool receiveVideo) { // Nothing to do here for now... } internal override void ProctoringInstructionReceived(InstructionEventArgs args) { if (args is ScreenProctoringInstruction instruction) { logger.Info($"Proctoring instruction received: {instruction.Method}."); if (instruction.Method == InstructionMethod.Join) { settings.ClientId = instruction.ClientId; settings.ClientSecret = instruction.ClientSecret; settings.GroupId = instruction.GroupId; settings.ServiceUrl = instruction.ServiceUrl; Connect(instruction.SessionId); Start(); } else { Stop(); } logger.Info("Successfully processed instruction."); } } internal override void Start() { collector.DataCollected += Collector_DataCollected; collector.Start(); spooler.Start(); UpdateNotification(true); logger.Info($"Started proctoring."); } internal override void Stop() { collector.Stop(); collector.DataCollected -= Collector_DataCollected; spooler.Stop(); TerminateSession(); UpdateNotification(false); logger.Info("Stopped proctoring."); } internal override void Terminate() { Stop(); logger.Info("Terminated proctoring."); } private void Collector_DataCollected(MetaData metaData, ScreenShot screenShot) { spooler.Add(metaData, screenShot); } private void Connect(string sessionId = default) { logger.Info("Connecting to service..."); var connect = service.Connect(settings.ClientId, settings.ClientSecret, settings.ServiceUrl); if (connect.Success) { if (sessionId == default) { logger.Info("Creating session..."); service.CreateSession(settings.GroupId); } else { service.SessionId = sessionId; } } } private void TerminateSession() { if (service.IsConnected) { logger.Info("Terminating session..."); service.TerminateSession(); } } private void UpdateNotification(bool live) { CanActivate = false; if (live) { IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ScreenProctoring_Active.xaml") }; Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip); } else { IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ScreenProctoring_Inactive.xaml") }; Tooltip = text.Get(TextKey.Notification_ProctoringInactiveTooltip); } InvokeNotificationChanged(); } } }