From 1f09b740f80dac1ab1e0b17519b2a7e4f3440fa4 Mon Sep 17 00:00:00 2001 From: Scott Date: Sun, 26 Apr 2020 12:14:42 +0100 Subject: Multi instance (#2791) WinGui: Refactoring and laying the groundwork in many parts of the UI code in preparation for supporting multiple concurrent jobs running. - Add support for multiple log files to the Activity log screen and refactor usages of ILog. Each Encode instance will now get a separate instance instead of there being one global instance. - Retire the Mini Display Window. It's very rarely used, not very easy to even find and of limited value. - Add the preferences code for the Simultaneous encode setting. Locked to one instance for now. This will allow us to test with users that existing functionality still works fine. --- win/CS/HandBrakeWPF/Services/Encode/EncodeBase.cs | 12 +- win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs | 100 +++----- .../Services/Logging/Interfaces/ILog.cs | 20 +- .../Logging/Interfaces/ILogInstanceManager.cs | 50 ++++ .../Services/Logging/LogInstanceManager.cs | 93 +++++++ win/CS/HandBrakeWPF/Services/Logging/LogService.cs | 117 ++------- .../HandBrakeWPF/Services/PrePostActionService.cs | 39 +-- win/CS/HandBrakeWPF/Services/Queue/ActiveJob.cs | 131 ++++++++++ .../Services/Queue/Interfaces/IQueueService.cs | 51 ++-- .../JobEventArgs/ActiveJobCompletedEventArgs.cs | 25 ++ .../Services/Queue/Model/QueueItemStatus.cs | 3 + .../Services/Queue/Model/QueueProgressStatus.cs | 133 ++++++++++ .../HandBrakeWPF/Services/Queue/Model/QueueTask.cs | 9 + win/CS/HandBrakeWPF/Services/Queue/QueueService.cs | 273 +++++++++++---------- win/CS/HandBrakeWPF/Services/SystemService.cs | 24 +- win/CS/HandBrakeWPF/Services/UserSettingService.cs | 1 + 16 files changed, 725 insertions(+), 356 deletions(-) create mode 100644 win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILogInstanceManager.cs create mode 100644 win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs create mode 100644 win/CS/HandBrakeWPF/Services/Queue/ActiveJob.cs create mode 100644 win/CS/HandBrakeWPF/Services/Queue/JobEventArgs/ActiveJobCompletedEventArgs.cs create mode 100644 win/CS/HandBrakeWPF/Services/Queue/Model/QueueProgressStatus.cs (limited to 'win/CS/HandBrakeWPF/Services') diff --git a/win/CS/HandBrakeWPF/Services/Encode/EncodeBase.cs b/win/CS/HandBrakeWPF/Services/Encode/EncodeBase.cs index 6d34a2efd..5563c2ed5 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/EncodeBase.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/EncodeBase.cs @@ -14,10 +14,8 @@ namespace HandBrakeWPF.Services.Encode using System.Globalization; using System.IO; - using HandBrake.Interop.Interop.EventArgs; using HandBrake.Interop.Model; - using HandBrakeWPF.Services.Encode.Interfaces; using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.Utilities; @@ -28,20 +26,18 @@ namespace HandBrakeWPF.Services.Encode using EncodeTask = HandBrakeWPF.Services.Encode.Model.EncodeTask; using GeneralApplicationException = HandBrakeWPF.Exceptions.GeneralApplicationException; using ILog = HandBrakeWPF.Services.Logging.Interfaces.ILog; - using LogService = HandBrakeWPF.Services.Logging.LogService; /// /// A Base Class for the Encode Services. /// public class EncodeBase { - private readonly ILog logService; + protected ILog encodeLogService; private readonly IUserSettingService userSettingService; - - public EncodeBase(ILog logService, IUserSettingService userSettingService) + + public EncodeBase(IUserSettingService userSettingService) { - this.logService = logService; this.userSettingService = userSettingService; } @@ -136,7 +132,7 @@ namespace HandBrakeWPF.Services.Encode string encodeDestinationPath = Path.GetDirectoryName(destination); string destinationFile = Path.GetFileName(destination); string encodeLogFile = destinationFile + " " + DateTime.Now.ToString(CultureInfo.InvariantCulture).Replace("/", "-").Replace(":", "-") + ".txt"; - string logContent = this.logService.GetFullLog(); + string logContent = this.encodeLogService.GetFullLog(); // Make sure the log directory exists. if (!Directory.Exists(logDir)) diff --git a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs index b71d06d3f..e6d8cb7c0 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs @@ -12,9 +12,9 @@ namespace HandBrakeWPF.Services.Encode using System; using System.Diagnostics; using System.IO; + using System.Windows.Forms.VisualStyles; using HandBrake.Interop.Interop.EventArgs; - using HandBrake.Interop.Interop.HbLib; using HandBrake.Interop.Interop.Interfaces; using HandBrake.Interop.Interop.Json.State; using HandBrake.Interop.Interop.Providers.Interfaces; @@ -24,11 +24,12 @@ namespace HandBrakeWPF.Services.Encode using HandBrakeWPF.Properties; using HandBrakeWPF.Services.Encode.Factories; using HandBrakeWPF.Services.Interfaces; + using HandBrakeWPF.Services.Logging.Interfaces; + using HandBrakeWPF.Utilities; using EncodeTask = Model.EncodeTask; using HandBrakeInstanceManager = Instance.HandBrakeInstanceManager; using IEncode = Interfaces.IEncode; - using ILog = Logging.Interfaces.ILog; using LogService = Logging.LogService; /// @@ -36,40 +37,27 @@ namespace HandBrakeWPF.Services.Encode /// public class LibEncode : EncodeBase, IEncode { - #region Private Variables - - private readonly ILog log; private readonly IUserSettingService userSettingService; + private readonly ILogInstanceManager logInstanceManager; private readonly IHbFunctionsProvider hbFunctionsProvider; private IEncodeInstance instance; private DateTime startTime; private EncodeTask currentTask; private HBConfiguration currentConfiguration; private bool isPreviewInstance; + private bool isLoggingInitialised; + private int encodeCounter; - #endregion - - public LibEncode(IHbFunctionsProvider hbFunctionsProvider, ILog logService, IUserSettingService userSettingService) : base(logService, userSettingService) + public LibEncode(IHbFunctionsProvider hbFunctionsProvider, IUserSettingService userSettingService, ILogInstanceManager logInstanceManager, int encodeCounter) : base(userSettingService) { - this.log = logService; this.userSettingService = userSettingService; + this.logInstanceManager = logInstanceManager; this.hbFunctionsProvider = hbFunctionsProvider; + this.encodeCounter = encodeCounter; } - /// - /// Gets a value indicating whether is pasued. - /// public bool IsPasued { get; private set; } - /// - /// Start with a LibHb EncodeJob Object - /// - /// - /// The task. - /// - /// - /// The configuration. - /// public void Start(EncodeTask task, HBConfiguration configuration, string basePresetName) { try @@ -85,10 +73,17 @@ namespace HandBrakeWPF.Services.Encode this.currentTask = task; this.currentConfiguration = configuration; - // Create a new HandBrake instance - // Setup the HandBrake Instance - this.log.Reset(); // Reset so we have a clean log for the start of the encode. + if (this.userSettingService.GetUserSetting(UserSettingConstants.ProcessIsolationEnabled)) + { + this.InitLogging(task.IsPreviewEncode); + } + else + { + this.encodeLogService = this.logInstanceManager.MasterLogInstance; + this.encodeLogService.Reset(); + } + if (this.instance != null) { // Cleanup @@ -112,7 +107,7 @@ namespace HandBrakeWPF.Services.Encode } int verbosity = this.userSettingService.GetUserSetting(UserSettingConstants.Verbosity); - this.instance = task.IsPreviewEncode ? HandBrakeInstanceManager.GetPreviewInstance(verbosity, this.userSettingService) : HandBrakeInstanceManager.GetEncodeInstance(verbosity, configuration, this.log, userSettingService); + this.instance = task.IsPreviewEncode ? HandBrakeInstanceManager.GetPreviewInstance(verbosity, this.userSettingService) : HandBrakeInstanceManager.GetEncodeInstance(verbosity, configuration, this.encodeLogService, userSettingService); this.instance.EncodeCompleted += this.InstanceEncodeCompleted; this.instance.EncodeProgress += this.InstanceEncodeProgress; @@ -138,9 +133,6 @@ namespace HandBrakeWPF.Services.Encode } } - /// - /// Pause the currently running encode. - /// public void Pause() { if (this.instance != null) @@ -151,9 +143,6 @@ namespace HandBrakeWPF.Services.Encode } } - /// - /// Resume the currently running encode. - /// public void Resume() { if (this.instance != null) @@ -164,9 +153,6 @@ namespace HandBrakeWPF.Services.Encode } } - /// - /// Kill the process - /// public void Stop() { try @@ -188,38 +174,23 @@ namespace HandBrakeWPF.Services.Encode { if (this.currentTask != null) { - EncodeTask task = new EncodeTask(this.currentTask); // Decouple our current copy. + EncodeTask task = new EncodeTask(this.currentTask); // Shallow copy our current instance. return task; } return null; } - #region HandBrakeInstance Event Handlers. - - /// - /// Service Log Message. - /// - /// Log message content protected void ServiceLogMessage(string message) { - this.log.LogMessage(string.Format("{0}# {1}", Environment.NewLine, message)); + this.encodeLogService.LogMessage(string.Format("{0}# {1}", Environment.NewLine, message)); } protected void TimedLogMessage(string message) { - this.log.LogMessage(string.Format("[{0}] {1}", DateTime.Now.ToString("hh:mm:ss"), message)); + this.encodeLogService.LogMessage(string.Format("[{0}] {1}", DateTime.Now.ToString("hh:mm:ss"), message)); } - /// - /// Encode Progress Event Handler - /// - /// - /// The sender. - /// - /// - /// The Interop.EncodeProgressEventArgs. - /// private void InstanceEncodeProgress(object sender, EncodeProgressEventArgs e) { EventArgs.EncodeProgressEventArgs args = new EventArgs.EncodeProgressEventArgs @@ -238,16 +209,7 @@ namespace HandBrakeWPF.Services.Encode this.InvokeEncodeStatusChanged(args); } - - /// - /// Encode Completed Event Handler - /// - /// - /// The sender. - /// - /// - /// The e. - /// + private void InstanceEncodeCompleted(object sender, EncodeCompletedEventArgs e) { this.IsEncoding = false; @@ -304,6 +266,18 @@ namespace HandBrakeWPF.Services.Encode return 0; } - #endregion + private void InitLogging(bool isPreview) + { + if (!isLoggingInitialised) + { + string logType = isPreview ? "preview" : "encode"; + string filename = string.Format("activity_log.{0}.{1}.{2}.txt", encodeCounter, logType, GeneralUtilities.ProcessId); + string logFile = Path.Combine(DirectoryUtilities.GetLogDirectory(), filename); + this.encodeLogService = new LogService(); + this.encodeLogService.ConfigureLogging(logFile); + this.logInstanceManager.RegisterLoggerInstance(filename, this.encodeLogService, false); + isLoggingInitialised = true; + } + } } } diff --git a/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILog.cs b/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILog.cs index d82a1ed0d..57594528a 100644 --- a/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILog.cs +++ b/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILog.cs @@ -12,17 +12,14 @@ namespace HandBrakeWPF.Services.Logging.Interfaces using System; using System.Collections.Generic; - using HandBrake.Worker.Logging.Interfaces; using HandBrake.Worker.Logging.Models; - using HandBrakeWPF.Services.Logging.Model; - using LogEventArgs = HandBrakeWPF.Services.Logging.EventArgs.LogEventArgs; /// /// The Log interface. /// - public interface ILog : ILogHandler + public interface ILog { /// /// The message logged. @@ -37,13 +34,13 @@ namespace HandBrakeWPF.Services.Logging.Interfaces /// /// Enable logging for this worker process. /// - /// - /// Configuration for the logger. + /// + /// The filename. /// /// /// If this is not called, all log messages from libhb will be ignored. /// - void ConfigureLogging(LogHandlerConfig config); + void ConfigureLogging(string filename); /// /// Log a message. @@ -52,5 +49,14 @@ namespace HandBrakeWPF.Services.Logging.Interfaces /// The content of the log message, /// void LogMessage(string content); + + string GetFullLog(); + + List GetLogMessages(); + + /// + /// Empty the log cache and reset the log handler to defaults. + /// + void Reset(); } } \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILogInstanceManager.cs b/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILogInstanceManager.cs new file mode 100644 index 000000000..c8af64601 --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/Logging/Interfaces/ILogInstanceManager.cs @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// Defines the ILogInstanceManager type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Logging.Interfaces +{ + using System; + using System.Collections.Generic; + + public interface ILogInstanceManager + { + event EventHandler NewLogInstanceRegistered; + + string ApplicationAndScanLog { get; } + + ILog MasterLogInstance { get; } + + /// + /// Register an ILog instance. + /// + /// + /// This is the key associated with the log instance. + /// + /// + /// The ILog instance + /// + /// + /// True indicates it's the log instance for the parent handbrake process. + /// + void RegisterLoggerInstance(string filename, ILog log, bool isMaster); + + /// + /// Gets a list of files without their associated ILog instances. + /// + /// List of filenames being logged + List GetLogFiles(); + + /// + /// Get the ILog instance for a given filename key + /// + /// The key of the log instance + /// An ILog instance or null if invalid key + ILog GetLogInstance(string filename); + } +} diff --git a/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs b/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs new file mode 100644 index 000000000..4e1290024 --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Logging +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using HandBrakeWPF.Services.Interfaces; + using HandBrakeWPF.Services.Logging.Interfaces; + + using Microsoft.Win32.SafeHandles; + + public class LogInstanceManager : ILogInstanceManager + { + private Dictionary logInstances = new Dictionary(); + + private int maxInstances; + + public LogInstanceManager(IUserSettingService userSettingService) + { + this.maxInstances = userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + } + + public event EventHandler NewLogInstanceRegistered; + + public string ApplicationAndScanLog { get; private set; } + + public ILog MasterLogInstance { get; private set; } + + public void RegisterLoggerInstance(string filename, ILog log, bool isMaster) + { + if (string.IsNullOrEmpty(this.ApplicationAndScanLog)) + { + // The application startup sets the initial log file. + this.ApplicationAndScanLog = filename; + } + + this.logInstances.Add(filename, log); + + this.CleanupInstance(); + + if (isMaster) + { + this.MasterLogInstance = log; + } + + this.OnNewLogInstanceRegistered(); + } + + public List GetLogFiles() + { + return this.logInstances.Keys.ToList(); + } + + public ILog GetLogInstance(string filename) + { + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + ILog logger; + if (this.logInstances.TryGetValue(filename, out logger)) + { + return logger; + } + + return null; + } + + protected virtual void OnNewLogInstanceRegistered() + { + this.NewLogInstanceRegistered?.Invoke(this, System.EventArgs.Empty); + } + + private void CleanupInstance() + { + List encodeLogs = this.logInstances.Keys.Where(f => f.Contains(".encode.")).ToList(); + string removalKey = this.logInstances.Keys.OrderBy(k => k).FirstOrDefault(w => w.Contains(".encode.")); + if (encodeLogs.Count > this.maxInstances) + { + this.logInstances.Remove(removalKey); + } + } + } +} diff --git a/win/CS/HandBrakeWPF/Services/Logging/LogService.cs b/win/CS/HandBrakeWPF/Services/Logging/LogService.cs index 738cb2b3a..18a8ffaee 100644 --- a/win/CS/HandBrakeWPF/Services/Logging/LogService.cs +++ b/win/CS/HandBrakeWPF/Services/Logging/LogService.cs @@ -18,47 +18,30 @@ namespace HandBrakeWPF.Services.Logging using System.IO; using System.Linq; using System.Text; - using System.Timers; - using HandBrake.Interop.Interop; - using HandBrake.Interop.Interop.EventArgs; - using HandBrake.Worker.Logging.Interfaces; using HandBrake.Worker.Logging.Models; - using HandBrakeWPF.Instance; - using HandBrakeWPF.Instance.Model; - using HandBrakeWPF.Services.Interfaces; - using HandBrakeWPF.Services.Logging.Model; using HandBrakeWPF.Utilities; - using Newtonsoft.Json; - using ILog = Interfaces.ILog; using LogEventArgs = EventArgs.LogEventArgs; - public class LogService : ILog + public class LogService : ILog, IDisposable { - // TODO List. - // Maybe make the event weak? - // Make this class Thread Safe. - private static ILog loggerInstance; private readonly object lockObject = new object(); private readonly object fileWriterLock = new object(); private readonly StringBuilder logBuilder = new StringBuilder(); - + private readonly List logMessages = new List(); + private bool isLoggingEnabled; - private List logMessages = new List(); private int messageIndex; private string diskLogPath; - private bool deleteLogFirst; private bool isDiskLoggingEnabled; private StreamWriter fileWriter; private string logHeader; - public LogService(IUserSettingService userSettingService) + public LogService() { - HandBrakeUtils.MessageLogged += this.HandBrakeUtils_MessageLogged; - HandBrakeUtils.ErrorLogged += this.HandBrakeUtils_ErrorLogged; } public event EventHandler MessageLogged; @@ -86,7 +69,7 @@ namespace HandBrakeWPF.Services.Logging LogMessage msg = new LogMessage(content, this.messageIndex); lock (this.lockObject) { - this.messageIndex = this.messageIndex + 1; + this.messageIndex = this.messageIndex + 1; this.logMessages.Add(msg); this.logBuilder.AppendLine(msg.Content); this.LogMessageToDisk(msg); @@ -106,22 +89,19 @@ namespace HandBrakeWPF.Services.Logging this.OnMessageLogged(msg); // Must be outside lock to be thread safe. } - public void ConfigureLogging(LogHandlerConfig config) + public void ConfigureLogging(string filename) { this.isLoggingEnabled = true; - if (config.EnableDiskLogging) + if (!string.IsNullOrEmpty(filename) && !Directory.Exists(Path.GetDirectoryName(filename))) { - if (!string.IsNullOrEmpty(config.LogFile) && !Directory.Exists(Path.GetDirectoryName(config.LogFile))) - { - Directory.CreateDirectory(Path.GetDirectoryName(config.LogFile)); - } - - this.EnableLoggingToDisk(config.LogFile, config.DeleteCurrentLogFirst); + Directory.CreateDirectory(Path.GetDirectoryName(filename)); } - this.logHeader = config.Header; - this.LogMessage(config.Header); + this.EnableLoggingToDisk(filename); + + this.logHeader = GeneralUtilities.CreateLogHeader().ToString(); + this.LogMessage(logHeader); } public string GetFullLog() @@ -155,14 +135,6 @@ namespace HandBrakeWPF.Services.Logging return log; } - public long GetLatestLogIndex() - { - lock (this.lockObject) - { - return this.messageIndex; - } - } - public async void Reset() { lock (this.lockObject) @@ -170,30 +142,13 @@ namespace HandBrakeWPF.Services.Logging this.logMessages.Clear(); this.logBuilder.Clear(); this.messageIndex = 0; - - try - { - lock (this.fileWriterLock) - { - if (this.fileWriter != null) - { - this.fileWriter.Flush(); - this.fileWriter.Close(); - this.fileWriter.Dispose(); - } - - this.fileWriter = null; - } - } - catch (Exception exc) - { - Debug.WriteLine(exc); - } + + this.ShutdownFileWriter(); if (this.fileWriter == null) { this.isDiskLoggingEnabled = false; - this.EnableLoggingToDisk(this.diskLogPath, this.deleteLogFirst); + this.EnableLoggingToDisk(this.diskLogPath); } if (!string.IsNullOrEmpty(this.logHeader)) @@ -205,16 +160,18 @@ namespace HandBrakeWPF.Services.Logging } } + public void Dispose() + { + this.ShutdownFileWriter(); + } + protected virtual void OnMessageLogged(LogMessage msg) { var onMessageLogged = this.MessageLogged; - if (onMessageLogged != null) - { - onMessageLogged.Invoke(this, new LogEventArgs(msg)); - } + onMessageLogged?.Invoke(this, new LogEventArgs(msg)); } - protected void ShutdownFileWriter() + private void ShutdownFileWriter() { try { @@ -241,7 +198,7 @@ namespace HandBrakeWPF.Services.Logging this.LogReset?.Invoke(this, System.EventArgs.Empty); } - private void EnableLoggingToDisk(string logFile, bool deleteCurrentLogFirst) + private void EnableLoggingToDisk(string logFile) { if (this.isDiskLoggingEnabled) { @@ -255,14 +212,13 @@ namespace HandBrakeWPF.Services.Logging throw new Exception("Log Directory does not exist. This service will not create it for you!"); } - if (deleteCurrentLogFirst && File.Exists(logFile)) + if (File.Exists(logFile)) { File.Delete(logFile); } this.diskLogPath = logFile; this.isDiskLoggingEnabled = true; - this.deleteLogFirst = deleteCurrentLogFirst; lock (this.fileWriterLock) { @@ -313,30 +269,5 @@ namespace HandBrakeWPF.Services.Logging Debug.WriteLine(exc); // This exception doesn't warrant user interaction, but it should be logged } } - - private void HandBrakeUtils_ErrorLogged(object sender, MessageLoggedEventArgs e) - { - if (e == null || string.IsNullOrEmpty(e.Message)) - { - return; - } - - this.LogMessage(e.Message); - } - - private void HandBrakeUtils_MessageLogged(object sender, MessageLoggedEventArgs e) - { - if (e == null || string.IsNullOrEmpty(e.Message)) - { - return; - } - - this.LogMessage(e.Message); - } - - void ILogHandler.ShutdownFileWriter() - { - throw new NotImplementedException(); - } } } diff --git a/win/CS/HandBrakeWPF/Services/PrePostActionService.cs b/win/CS/HandBrakeWPF/Services/PrePostActionService.cs index e8118f2a3..2eba7b38c 100644 --- a/win/CS/HandBrakeWPF/Services/PrePostActionService.cs +++ b/win/CS/HandBrakeWPF/Services/PrePostActionService.cs @@ -34,7 +34,6 @@ namespace HandBrakeWPF.Services public class PrePostActionService : IPrePostActionService { private readonly ILog log; - private readonly IQueueService queueProcessor; private readonly IUserSettingService userSettingService; private readonly IWindowManager windowManager; private readonly IScan scanService; @@ -42,14 +41,26 @@ namespace HandBrakeWPF.Services public PrePostActionService(IQueueService queueProcessor, IUserSettingService userSettingService, IWindowManager windowManager, IScan scanService, ILog logService) { this.log = logService; - this.queueProcessor = queueProcessor; this.userSettingService = userSettingService; this.windowManager = windowManager; this.scanService = scanService; - this.queueProcessor.QueueCompleted += this.QueueProcessorQueueCompleted; - this.queueProcessor.EncodeService.EncodeCompleted += this.EncodeService_EncodeCompleted; - this.queueProcessor.EncodeService.EncodeStarted += this.EncodeService_EncodeStarted; + queueProcessor.QueueCompleted += this.QueueProcessorQueueCompleted; + queueProcessor.QueuePaused += this.QueueProcessor_QueuePaused; + queueProcessor.EncodeCompleted += this.EncodeService_EncodeCompleted; + queueProcessor.JobProcessingStarted += this.EncodeService_EncodeStarted; + } + + private void QueueProcessor_QueuePaused(object sender, EventArgs e) + { + // Allow the system to sleep again. + Execute.OnUIThread(() => + { + if (this.userSettingService.GetUserSetting(UserSettingConstants.PreventSleep)) + { + Win32.AllowSleep(); + } + }); } /// @@ -90,15 +101,6 @@ namespace HandBrakeWPF.Services { this.PlayWhenDoneSound(); } - - // Allow the system to sleep again. - Execute.OnUIThread(() => - { - if (this.userSettingService.GetUserSetting(UserSettingConstants.PreventSleep)) - { - Win32.AllowSleep(); - } - }); } /// @@ -172,6 +174,15 @@ namespace HandBrakeWPF.Services break; } } + + // Allow the system to sleep again. + Execute.OnUIThread(() => + { + if (this.userSettingService.GetUserSetting(UserSettingConstants.PreventSleep)) + { + Win32.AllowSleep(); + } + }); } private void SendToApplication(string source, string destination) diff --git a/win/CS/HandBrakeWPF/Services/Queue/ActiveJob.cs b/win/CS/HandBrakeWPF/Services/Queue/ActiveJob.cs new file mode 100644 index 000000000..10fffcbf0 --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/Queue/ActiveJob.cs @@ -0,0 +1,131 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// Defines the ActiveJob type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Queue +{ + using System; + + using HandBrake.Interop.Interop.Providers.Interfaces; + + using HandBrakeWPF.Services.Encode; + using HandBrakeWPF.Services.Encode.EventArgs; + using HandBrakeWPF.Services.Encode.Interfaces; + using HandBrakeWPF.Services.Interfaces; + using HandBrakeWPF.Services.Logging.Interfaces; + using HandBrakeWPF.Services.Queue.JobEventArgs; + using HandBrakeWPF.Services.Queue.Model; + + public class ActiveJob : IDisposable + { + private readonly QueueTask job; + private readonly IEncode encodeService; + + public ActiveJob(QueueTask task, IHbFunctionsProvider hbFunctionsProvider, IUserSettingService userSettingService, ILogInstanceManager logInstanceManager, int jobId) + { + this.job = task; + this.encodeService = new LibEncode(hbFunctionsProvider, userSettingService, logInstanceManager, jobId); + } + + public event EventHandler JobFinished; + + public event EventHandler JobStatusUpdated; + + public QueueTask Job => this.job; + + public bool IsPaused { get; private set; } + + public bool IsEncoding { get; set; } + + public void Start() + { + this.IsPaused = false; + this.IsEncoding = true; + + if (this.encodeService.IsPasued) + { + this.encodeService.Resume(); + this.job.Statistics.SetPaused(false); + this.job.Status = QueueItemStatus.InProgress; + } + else if (!this.encodeService.IsEncoding) + { + this.job.Status = QueueItemStatus.InProgress; + this.job.Statistics.StartTime = DateTime.Now; + + this.encodeService.EncodeCompleted += this.EncodeServiceEncodeCompleted; + this.encodeService.EncodeStatusChanged += this.EncodeStatusChanged; + this.encodeService.Start(this.job.Task, this.job.Configuration, this.job.SelectedPresetKey); + } + } + + public void Pause() + { + if (this.encodeService.IsEncoding && !this.encodeService.IsPasued) + { + this.IsPaused = true; + this.encodeService.Pause(); + this.job.Statistics.SetPaused(true); + this.job.Status = QueueItemStatus.Paused; + this.IsEncoding = false; + } + } + + public void Stop() + { + if (this.encodeService.IsEncoding) + { + this.encodeService.Stop(); + } + + this.IsEncoding = false; + this.IsPaused = false; + this.encodeService.EncodeStatusChanged -= this.EncodeStatusChanged; + } + + public void Dispose() + { + this.encodeService.EncodeCompleted -= this.EncodeServiceEncodeCompleted; + this.encodeService.EncodeStatusChanged -= this.EncodeStatusChanged; + } + + private void EncodeStatusChanged(object sender, EncodeProgressEventArgs e) + { + this.job?.JobProgress.Update(e); + this.OnJobStatusUpdated(e); + } + + private void EncodeServiceEncodeCompleted(object sender, EncodeCompletedEventArgs e) + { + this.IsEncoding = false; + this.IsPaused = false; + + this.job.Status = !e.Successful ? QueueItemStatus.Error : QueueItemStatus.Completed; + this.job.Statistics.EndTime = DateTime.Now; + this.job.Statistics.CompletedActivityLogPath = e.ActivityLogPath; + this.job.Statistics.FinalFileSize = e.FinalFilesizeInBytes; + + this.job.JobProgress.ClearStatusDisplay(); + + this.encodeService.EncodeStatusChanged -= this.EncodeStatusChanged; + this.encodeService.EncodeCompleted -= this.EncodeServiceEncodeCompleted; + + this.OnJobFinished(e); + } + + private void OnJobFinished(EncodeCompletedEventArgs e) + { + this.JobFinished?.Invoke(this, new ActiveJobCompletedEventArgs(this, e)); + } + + private void OnJobStatusUpdated(EncodeProgressEventArgs e) + { + this.JobStatusUpdated?.Invoke(this, e); + } + } +} diff --git a/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs index 1bab4f01d..1a2a04cce 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs @@ -10,24 +10,29 @@ namespace HandBrakeWPF.Services.Queue.Interfaces { using System; + using System.Collections.Generic; using System.Collections.ObjectModel; - using System.ComponentModel; - using HandBrakeWPF.Services.Queue.Model; - using IEncode = Encode.Interfaces.IEncode; + using HandBrakeWPF.EventArgs; + using HandBrakeWPF.Services.Encode.EventArgs; + using HandBrakeWPF.Services.Encode.Interfaces; + using HandBrakeWPF.Services.Queue.Model; /// /// The Queue Processor /// public interface IQueueService { - #region Events - /// /// Fires when the Queue has started /// - event QueueService.QueueProgressStatus JobProcessingStarted; + event EventHandler JobProcessingStarted; + + /// + /// Fires when the status of any running job changes on the queue. Including progress. + /// + event EventHandler QueueJobStatusChanged; /// /// Fires when a job is Added, Removed or Re-Ordered. @@ -38,16 +43,14 @@ namespace HandBrakeWPF.Services.Queue.Interfaces /// /// Fires when the entire encode queue has completed. /// - event QueueService.QueueCompletedEventDelegate QueueCompleted; + event EventHandler QueueCompleted; /// /// Fires when a pause to the encode queue has been requested. /// event EventHandler QueuePaused; - #endregion - - #region Properties + event EventHandler EncodeCompleted; /// /// Gets the number of jobs in the queue @@ -59,31 +62,20 @@ namespace HandBrakeWPF.Services.Queue.Interfaces /// int ErrorCount { get; } - /// - /// Gets the IEncodeService instance. - /// - IEncode EncodeService { get; } - /// /// Gets a value indicating whether IsProcessing. /// bool IsProcessing { get; } - /// - /// Gets or sets Last Processed Job. - /// This is set when the job is poped of the queue by GetNextJobForProcessing(); - /// - QueueTask LastProcessedJob { get; set; } + bool IsEncoding { get; } + + bool IsPaused { get; } /// /// Gets The current queue. /// ObservableCollection Queue { get; } - #endregion - - #region Public Methods - /// /// Add a job to the Queue. /// This method is Thread Safe. @@ -223,12 +215,15 @@ namespace HandBrakeWPF.Services.Queue.Interfaces /// Pause the queue but allow the current encode to complete. /// void Pause(); - + /// - /// Pause and Encode and the Queue. + /// Get the status of all running queue jobs. /// - void PauseEncode(); + /// + /// A list of QueueProgressStatus items + /// + List GetQueueProgressStatus(); - #endregion + List GetActiveJobDestinationDirectories(); } } \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Services/Queue/JobEventArgs/ActiveJobCompletedEventArgs.cs b/win/CS/HandBrakeWPF/Services/Queue/JobEventArgs/ActiveJobCompletedEventArgs.cs new file mode 100644 index 000000000..dae67e640 --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/Queue/JobEventArgs/ActiveJobCompletedEventArgs.cs @@ -0,0 +1,25 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Queue.JobEventArgs +{ + using System; + + using HandBrakeWPF.Services.Encode.EventArgs; + + public class ActiveJobCompletedEventArgs : EventArgs + { + public ActiveJobCompletedEventArgs(ActiveJob job, EncodeCompletedEventArgs encodeCompletedEventArgs) + { + this.EncodeEventArgs = encodeCompletedEventArgs; + this.Job = job; + } + + public ActiveJob Job { get; private set; } + + public EncodeCompletedEventArgs EncodeEventArgs { get; private set; } + } +} diff --git a/win/CS/HandBrakeWPF/Services/Queue/Model/QueueItemStatus.cs b/win/CS/HandBrakeWPF/Services/Queue/Model/QueueItemStatus.cs index c398e6a32..d7d0dbce2 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/Model/QueueItemStatus.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/Model/QueueItemStatus.cs @@ -27,5 +27,8 @@ namespace HandBrakeWPF.Services.Queue.Model [DisplayName("Error")] Error, + + [DisplayName("Paused")] + Paused, } } diff --git a/win/CS/HandBrakeWPF/Services/Queue/Model/QueueProgressStatus.cs b/win/CS/HandBrakeWPF/Services/Queue/Model/QueueProgressStatus.cs new file mode 100644 index 000000000..f759dfeb8 --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/Queue/Model/QueueProgressStatus.cs @@ -0,0 +1,133 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// Defines the QueueProgressStatus type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Queue.Model +{ + using System; + using System.Xaml; + + using Caliburn.Micro; + + using HandBrakeWPF.Properties; + using HandBrakeWPF.Services.Encode.EventArgs; + + public class QueueProgressStatus : PropertyChangedBase + { + private string jobStatus; + private bool intermediateProgress; + private double progressValue; + + private EncodeProgressEventArgs progressEventArgs; + + public QueueProgressStatus() + { + } + + public string JobStatus + { + get + { + return this.jobStatus; + } + + set + { + this.jobStatus = value; + this.NotifyOfPropertyChange(() => this.JobStatus); + } + } + + public bool IntermediateProgress + { + get => this.intermediateProgress; + set + { + if (value == this.intermediateProgress) return; + this.intermediateProgress = value; + this.NotifyOfPropertyChange(() => this.IntermediateProgress); + } + } + + public double ProgressValue + { + get => this.progressValue; + set + { + if (value == this.progressValue) return; + this.progressValue = value; + this.NotifyOfPropertyChange(() => this.ProgressValue); + } + } + + public int? Task => this.progressEventArgs?.Task ?? 0; + + public int? TaskCount => this.progressEventArgs?.TaskCount ?? 0; + + public TimeSpan? EstimatedTimeLeft => this.progressEventArgs?.EstimatedTimeLeft ?? null; + + public void Update(EncodeProgressEventArgs e) + { + progressEventArgs = e; + this.IntermediateProgress = false; + + string totalHrsLeft = e.EstimatedTimeLeft.Days >= 1 ? string.Format(@"{0:d\:hh\:mm\:ss}", e.EstimatedTimeLeft) : string.Format(@"{0:hh\:mm\:ss}", e.EstimatedTimeLeft); + string elapsedTimeHrs = e.ElapsedTime.Days >= 1 ? string.Format(@"{0:d\:hh\:mm\:ss}", e.ElapsedTime) : string.Format(@"{0:hh\:mm\:ss}", e.ElapsedTime); + + if (e.IsSubtitleScan) + { + this.JobStatus = string.Format(Resources.MainViewModel_EncodeStatusChanged_SubScan_StatusLabel, + e.Task, + e.TaskCount, + e.PercentComplete, + totalHrsLeft, + elapsedTimeHrs, + null); + + this.ProgressValue = e.PercentComplete; + } + else if (e.IsMuxing) + { + this.JobStatus = Resources.MainView_Muxing; + this.IntermediateProgress = true; + } + else if (e.IsSearching) + { + this.JobStatus = string.Format(Resources.MainView_ProgressStatusWithTask, Resources.MainView_Searching, e.PercentComplete, e.EstimatedTimeLeft, null); + this.ProgressValue = e.PercentComplete; + } + else + { + this.JobStatus = + string.Format(Resources.QueueViewModel_EncodeStatusChanged_StatusLabel, + e.Task, + e.TaskCount, + e.PercentComplete, + e.CurrentFrameRate, + e.AverageFrameRate, + totalHrsLeft, + elapsedTimeHrs, + null); + this.ProgressValue = e.PercentComplete; + } + } + + public void SetPaused() + { + this.ClearStatusDisplay(); + this.JobStatus = Resources.QueueViewModel_QueuePaused; + } + + public void ClearStatusDisplay() + { + this.JobStatus = string.Empty; + this.ProgressValue = 0; + this.IntermediateProgress = false; + } + } +} diff --git a/win/CS/HandBrakeWPF/Services/Queue/Model/QueueTask.cs b/win/CS/HandBrakeWPF/Services/Queue/Model/QueueTask.cs index 3ee126ce9..208c721e4 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/Model/QueueTask.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/Model/QueueTask.cs @@ -35,6 +35,7 @@ namespace HandBrakeWPF.Services.Queue.Model id = id + 1; this.Id = string.Format("{0}.{1}", GeneralUtilities.ProcessId, id); this.Statistics = new QueueStats(); + this.JobProgress = new QueueProgressStatus(); } public QueueTask(EncodeTask task, HBConfiguration configuration, string scannedSourcePath, Preset currentPreset, bool isPresetModified) @@ -57,6 +58,7 @@ namespace HandBrakeWPF.Services.Queue.Model this.Statistics = new QueueStats(); this.TaskId = Guid.NewGuid().ToString(); + this.JobProgress = new QueueProgressStatus(); } [JsonProperty] @@ -81,6 +83,7 @@ namespace HandBrakeWPF.Services.Queue.Model this.status = value; this.NotifyOfPropertyChange(() => this.Status); this.NotifyOfPropertyChange(() => this.ShowEncodeProgress); + this.NotifyOfPropertyChange(() => this.IsJobStatusVisible); } } @@ -93,6 +96,12 @@ namespace HandBrakeWPF.Services.Queue.Model [JsonProperty] public QueueStats Statistics { get; set; } + [JsonIgnore] + public QueueProgressStatus JobProgress { get; set; } + + [JsonIgnore] + public bool IsJobStatusVisible => this.Status == QueueItemStatus.InProgress; + [JsonIgnore] public string SelectedPresetKey { diff --git a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs index 9ebd4d33b..6cbf709e5 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs @@ -12,12 +12,10 @@ namespace HandBrakeWPF.Services.Queue using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Windows; - using System.Windows.Media.Imaging; using Caliburn.Micro; @@ -33,6 +31,9 @@ namespace HandBrakeWPF.Services.Queue using HandBrakeWPF.Services.Encode.Factories; using HandBrakeWPF.Services.Encode.Model; using HandBrakeWPF.Services.Interfaces; + using HandBrakeWPF.Services.Logging.Interfaces; + using HandBrakeWPF.Services.Queue.Interfaces; + using HandBrakeWPF.Services.Queue.JobEventArgs; using HandBrakeWPF.Services.Queue.Model; using HandBrakeWPF.Utilities; @@ -41,46 +42,53 @@ namespace HandBrakeWPF.Services.Queue using EncodeCompletedEventArgs = HandBrakeWPF.Services.Encode.EventArgs.EncodeCompletedEventArgs; using Execute = Caliburn.Micro.Execute; using GeneralApplicationException = HandBrakeWPF.Exceptions.GeneralApplicationException; - using IEncode = HandBrakeWPF.Services.Encode.Interfaces.IEncode; using ILog = HandBrakeWPF.Services.Logging.Interfaces.ILog; - using LogService = HandBrakeWPF.Services.Logging.LogService; using QueueCompletedEventArgs = HandBrakeWPF.EventArgs.QueueCompletedEventArgs; using QueueProgressEventArgs = HandBrakeWPF.EventArgs.QueueProgressEventArgs; - public class QueueService : Interfaces.IQueueService + public class QueueService : IQueueService { private static readonly object QueueLock = new object(); + + private readonly List activeJobs = new List(); private readonly IUserSettingService userSettingService; private readonly ILog logService; private readonly IErrorService errorService; + private readonly ILogInstanceManager logInstanceManager; + private readonly IHbFunctionsProvider hbFunctionsProvider; private readonly ObservableCollection queue = new ObservableCollection(); private readonly string queueFile; private bool clearCompleted; + private int allowedInstances; + private int jobIdCounter = 0; - public QueueService(IEncode encodeService, IUserSettingService userSettingService, ILog logService, IErrorService errorService) + public QueueService(IUserSettingService userSettingService, ILog logService, IErrorService errorService, ILogInstanceManager logInstanceManager, IHbFunctionsProvider hbFunctionsProvider) { this.userSettingService = userSettingService; this.logService = logService; this.errorService = errorService; - this.EncodeService = encodeService; + this.logInstanceManager = logInstanceManager; + this.hbFunctionsProvider = hbFunctionsProvider; // If this is the first instance, just use the main queue file, otherwise add the instance id to the filename. this.queueFile = string.Format("{0}{1}.json", QueueRecoveryHelper.QueueFileName, GeneralUtilities.ProcessId); - } - public delegate void QueueProgressStatus(object sender, QueueProgressEventArgs e); - - public delegate void QueueCompletedEventDelegate(object sender, QueueCompletedEventArgs e); + this.allowedInstances = this.userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + } - public event QueueProgressStatus JobProcessingStarted; + public event EventHandler JobProcessingStarted; public event EventHandler QueueChanged; - public event QueueCompletedEventDelegate QueueCompleted; + public event EventHandler QueueCompleted; public event EventHandler QueuePaused; + public event EventHandler QueueJobStatusChanged; + + public event EventHandler EncodeCompleted; + public int Count { get @@ -97,11 +105,11 @@ namespace HandBrakeWPF.Services.Queue } } - public IEncode EncodeService { get; private set; } + public bool IsPaused { get; private set; } public bool IsProcessing { get; private set; } - public QueueTask LastProcessedJob { get; set; } + public bool IsEncoding => this.activeJobs.Any(service => service.IsEncoding); public ObservableCollection Queue { @@ -110,8 +118,7 @@ namespace HandBrakeWPF.Services.Queue return this.queue; } } - - + public void Add(QueueTask job) { lock (QueueLock) @@ -227,7 +234,7 @@ namespace HandBrakeWPF.Services.Queue foreach (QueueTask task in reloadedQueue) { // Reset the imported jobs that were running in a previous session. - if (task.Status == QueueItemStatus.InProgress) + if (task.Status == QueueItemStatus.InProgress || task.Status == QueueItemStatus.Paused) { task.Status = QueueItemStatus.Waiting; task.Statistics.Reset(); @@ -249,8 +256,7 @@ namespace HandBrakeWPF.Services.Queue } } } - - + public bool CheckForDestinationPathDuplicates(string destination) { foreach (QueueTask job in this.queue) @@ -396,7 +402,7 @@ namespace HandBrakeWPF.Services.Queue if (item.Status != QueueItemStatus.Completed) { // Reset InProgress/Error to Waiting so it can be processed - if (item.Status == QueueItemStatus.InProgress) + if (item.Status == QueueItemStatus.InProgress || item.Status == QueueItemStatus.Paused) { item.Status = QueueItemStatus.Error; } @@ -418,19 +424,17 @@ namespace HandBrakeWPF.Services.Queue public void Pause() { - this.IsProcessing = false; - this.InvokeQueuePaused(EventArgs.Empty); - } - - public void PauseEncode() - { - if (this.EncodeService.IsEncoding && !this.EncodeService.IsPasued) + foreach (ActiveJob job in this.activeJobs) { - this.EncodeService.Pause(); - this.LastProcessedJob.Statistics.SetPaused(true); + if (job.IsEncoding && !job.IsPaused) + { + job.Pause(); + } } - - this.Pause(); + + this.IsProcessing = false; + this.IsPaused = true; + this.InvokeQueuePaused(EventArgs.Empty); } public void Start(bool isClearCompleted) @@ -440,89 +444,63 @@ namespace HandBrakeWPF.Services.Queue return; } + this.IsPaused = false; this.clearCompleted = isClearCompleted; - this.EncodeService.EncodeCompleted -= this.EncodeServiceEncodeCompleted; - this.EncodeService.EncodeCompleted += this.EncodeServiceEncodeCompleted; - - if (this.EncodeService.IsPasued) + // Unpause all active jobs. + foreach (ActiveJob job in this.activeJobs) { - this.EncodeService.Resume(); - this.IsProcessing = true; - this.InvokeJobProcessingStarted(new QueueProgressEventArgs(this.LastProcessedJob)); - this.LastProcessedJob.Statistics.SetPaused(false); + job.Start(); + this.InvokeJobProcessingStarted(new QueueProgressEventArgs(job.Job)); } - if (!this.EncodeService.IsEncoding) - { - this.ProcessNextJob(); - } - else - { - this.IsProcessing = true; - } + this.ProcessNextJob(); + this.IsProcessing = true; } public void Stop() { - if (this.EncodeService.IsEncoding) + foreach (ActiveJob job in this.activeJobs) { - this.EncodeService.Stop(); + if (job.IsEncoding || job.IsPaused) + { + job.Stop(); + } } this.IsProcessing = false; - this.InvokeQueuePaused(EventArgs.Empty); + this.IsPaused = false; + this.InvokeQueueChanged(EventArgs.Empty); + this.InvokeQueueCompleted(new QueueCompletedEventArgs(true)); } - protected virtual void OnQueueCompleted(QueueCompletedEventArgs e) + public List GetQueueProgressStatus() { - QueueCompletedEventDelegate handler = this.QueueCompleted; - if (handler != null) + // TODO make thread safe. + List statuses = new List(); + foreach (ActiveJob job in this.activeJobs) { - handler(this, e); + statuses.Add(job.Job.JobProgress); } - this.IsProcessing = false; + return statuses; } - private void EncodeServiceEncodeCompleted(object sender, EncodeCompletedEventArgs e) + public List GetActiveJobDestinationDirectories() { - this.LastProcessedJob.Status = QueueItemStatus.Completed; - this.LastProcessedJob.Statistics.EndTime = DateTime.Now; - this.LastProcessedJob.Statistics.CompletedActivityLogPath = e.ActivityLogPath; - this.LastProcessedJob.Statistics.FinalFileSize = e.FinalFilesizeInBytes; - - // Clear the completed item of the queue if the setting is set. - if (this.clearCompleted) - { - this.ClearCompleted(); - } - - if (!e.Successful) + // TODO need to make thread safe. + List directories = new List(); + foreach (ActiveJob job in this.activeJobs) { - this.LastProcessedJob.Status = QueueItemStatus.Error; + directories.Add(job.Job.Task.Destination); } - // Move onto the next job. - if (this.IsProcessing) - { - this.ProcessNextJob(); - } - else - { - this.EncodeService.EncodeCompleted -= this.EncodeServiceEncodeCompleted; - this.BackupQueue(string.Empty); - this.OnQueueCompleted(new QueueCompletedEventArgs(true)); - } + return directories; } private void InvokeJobProcessingStarted(QueueProgressEventArgs e) { - QueueProgressStatus handler = this.JobProcessingStarted; - if (handler != null) - { - handler(this, e); - } + this.JobProcessingStarted?.Invoke(this, e); } private void InvokeQueueChanged(EventArgs e) @@ -542,73 +520,90 @@ namespace HandBrakeWPF.Services.Queue handler(this, e); } } - - private void InvokeQueuePaused(EventArgs e) + + private void ProcessNextJob() { - this.IsProcessing = false; - - EventHandler handler = this.QueuePaused; - if (handler != null) + if (this.activeJobs.Count >= this.allowedInstances) { - handler(this, e); + return; } - } - private void ProcessNextJob() - { QueueTask job = this.GetNextJobForProcessing(); if (job != null) { - if (this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(job.Task.Destination, this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel))) + if (CheckDiskSpace(job)) { - this.logService.LogMessage(Resources.PauseOnLowDiskspace); - job.Status = QueueItemStatus.Waiting; - this.Pause(); - this.BackupQueue(string.Empty); return; // Don't start the next job. } - job.Status = QueueItemStatus.InProgress; - job.Statistics.StartTime = DateTime.Now; - this.LastProcessedJob = job; + this.jobIdCounter = this.jobIdCounter + 1; + ActiveJob activeJob = new ActiveJob(job, this.hbFunctionsProvider, this.userSettingService, this.logInstanceManager, this.jobIdCounter); + activeJob.JobFinished += this.ActiveJob_JobFinished; + activeJob.JobStatusUpdated += this.ActiveJob_JobStatusUpdated; + this.activeJobs.Add(activeJob); + + activeJob.Start(); + this.IsProcessing = true; this.InvokeQueueChanged(EventArgs.Empty); this.InvokeJobProcessingStarted(new QueueProgressEventArgs(job)); - - if (!Directory.Exists(Path.GetDirectoryName(job.Task.Destination))) - { - this.EncodeServiceEncodeCompleted(null, new EncodeCompletedEventArgs(false, null, "Destination Directory Missing", null, null, null, 0)); - this.BackupQueue(string.Empty); - return; - } - - this.EncodeService.Start(job.Task, job.Configuration, job.SelectedPresetKey); this.BackupQueue(string.Empty); + + this.ProcessNextJob(); } else { - // No more jobs to process, so unsubscribe the event - this.EncodeService.EncodeCompleted -= this.EncodeServiceEncodeCompleted; - this.BackupQueue(string.Empty); // Fire the event to tell connected services. - this.OnQueueCompleted(new QueueCompletedEventArgs(false)); - } - } - - /// - /// For a given set of tasks, return the Queue JSON that can be used for the CLI. - /// - /// - /// The tasks. - /// - /// - /// The configuration. - /// - /// - /// The . - /// + this.InvokeQueueCompleted(new QueueCompletedEventArgs(false)); + } + } + + private void ActiveJob_JobStatusUpdated(object sender, Encode.EventArgs.EncodeProgressEventArgs e) + { + this.OnQueueJobStatusChanged(); + } + + private void ActiveJob_JobFinished(object sender, ActiveJobCompletedEventArgs e) + { + this.activeJobs.Remove(e.Job); + this.OnEncodeCompleted(e.EncodeEventArgs); + + if (!this.IsPaused && this.IsProcessing) + { + this.ProcessNextJob(); + } + } + + private void InvokeQueueCompleted(QueueCompletedEventArgs e) + { + this.IsProcessing = false; + this.QueueCompleted?.Invoke(this, e); + } + + private void OnQueueJobStatusChanged() + { + // TODO add support for delayed notificaitons here to avoid overloading the UI when we run multiple encodes. + this.QueueJobStatusChanged?.Invoke(this, EventArgs.Empty); + } + + private void OnEncodeCompleted(EncodeCompletedEventArgs e) + { + this.EncodeCompleted?.Invoke(this, e); + } + + private void InvokeQueuePaused(EventArgs e) + { + this.IsProcessing = false; + + EventHandler handler = this.QueuePaused; + if (handler != null) + { + handler(this, e); + } + } + private string GetQueueJson(List tasks, HBConfiguration configuration) { JsonSerializerSettings settings = new JsonSerializerSettings @@ -628,5 +623,19 @@ namespace HandBrakeWPF.Services.Queue return JsonConvert.SerializeObject(queueJobs, Formatting.Indented, settings); } + + private bool CheckDiskSpace(QueueTask job) + { + if (this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(job.Task.Destination, this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel))) + { + this.logService.LogMessage(Resources.PauseOnLowDiskspace); + job.Status = QueueItemStatus.Waiting; + this.Pause(); + this.BackupQueue(string.Empty); + return true; // Don't start the next job. + } + + return false; + } } } \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Services/SystemService.cs b/win/CS/HandBrakeWPF/Services/SystemService.cs index d9fa46716..e07281b3b 100644 --- a/win/CS/HandBrakeWPF/Services/SystemService.cs +++ b/win/CS/HandBrakeWPF/Services/SystemService.cs @@ -13,7 +13,6 @@ namespace HandBrakeWPF.Services using System.Timers; using HandBrakeWPF.Properties; - using HandBrakeWPF.Services.Encode.Interfaces; using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.Services.Logging.Interfaces; using HandBrakeWPF.Services.Queue.Interfaces; @@ -22,23 +21,20 @@ namespace HandBrakeWPF.Services public class SystemService : ISystemService { private readonly IUserSettingService userSettingService; - private readonly IEncode encodeService; - private readonly ILog log = null; private readonly IQueueService queueService; + private readonly ILog log; private Timer pollTimer; - private bool criticalStateHit = false; private bool lowStateHit = false; private bool lowPowerPause = false; private bool storageLowPause = false; - public SystemService(IUserSettingService userSettingService, IEncode encodeService, ILog logService, IQueueService queueService) + public SystemService(IUserSettingService userSettingService, ILog logService, IQueueService queueService) { this.log = logService; this.queueService = queueService; this.userSettingService = userSettingService; - this.encodeService = encodeService; } public void Start() @@ -64,8 +60,15 @@ namespace HandBrakeWPF.Services private void StorageCheck() { - string directory = this.encodeService.GetActiveJob()?.Destination; - if (!string.IsNullOrEmpty(directory) && this.encodeService.IsEncoding) + foreach (string directory in this.queueService.GetActiveJobDestinationDirectories()) + { + this.CheckDiskSpaceForDirectory(directory); + } + } + + private void CheckDiskSpaceForDirectory(string directory) + { + if (!string.IsNullOrEmpty(directory) && this.queueService.IsEncoding) { long lowLevel = this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel); if (!this.storageLowPause && this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(directory, lowLevel)) @@ -98,7 +101,7 @@ namespace HandBrakeWPF.Services if (state.ACLineStatus == Win32.ACLineStatus.Offline && state.BatteryLifePercent <= lowBatteryLevel && !this.lowStateHit) { - if (this.encodeService.IsEncoding && !this.encodeService.IsPasued) + if (this.queueService.IsEncoding && !this.queueService.IsPaused) { this.lowPowerPause = true; this.queueService.Pause(); @@ -113,14 +116,13 @@ namespace HandBrakeWPF.Services // Reset the flags when we start charging. if (state.ACLineStatus == Win32.ACLineStatus.Online) { - if (this.lowPowerPause && this.encodeService.IsPasued) + if (this.lowPowerPause && this.queueService.IsPaused) { this.queueService.Start(this.userSettingService.GetUserSetting(UserSettingConstants.ClearCompletedFromQueue)); this.ServiceLogMessage(string.Format(Resources.SystemService_ACMains, state.BatteryLifePercent)); } this.lowPowerPause = false; - this.criticalStateHit = false; this.lowStateHit = false; } } diff --git a/win/CS/HandBrakeWPF/Services/UserSettingService.cs b/win/CS/HandBrakeWPF/Services/UserSettingService.cs index 91ef1e5b7..095e6f819 100644 --- a/win/CS/HandBrakeWPF/Services/UserSettingService.cs +++ b/win/CS/HandBrakeWPF/Services/UserSettingService.cs @@ -306,6 +306,7 @@ namespace HandBrakeWPF.Services // Experimental defaults.Add(UserSettingConstants.ProcessIsolationEnabled, true); defaults.Add(UserSettingConstants.ProcessIsolationPort, 8037); + defaults.Add(UserSettingConstants.SimultaneousEncodes, 1); // Misc defaults.Add(UserSettingConstants.ShowPresetPanel, false); -- cgit v1.2.3