diff options
author | Scott <[email protected]> | 2020-04-26 12:14:42 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-04-26 12:14:42 +0100 |
commit | 1f09b740f80dac1ab1e0b17519b2a7e4f3440fa4 (patch) | |
tree | c6d3102080111ee699ef6f91c3571ff611a1a882 /win/CS/HandBrakeWPF/Services | |
parent | f8b3478d8b7a1c9284c4339b32ce8f7bdc5d54b3 (diff) |
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.
Diffstat (limited to 'win/CS/HandBrakeWPF/Services')
16 files changed, 725 insertions, 356 deletions
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; /// <summary> /// A Base Class for the Encode Services. /// </summary> 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; /// <summary> @@ -36,40 +37,27 @@ namespace HandBrakeWPF.Services.Encode /// </summary> 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; } - /// <summary> - /// Gets a value indicating whether is pasued. - /// </summary> public bool IsPasued { get; private set; } - /// <summary> - /// Start with a LibHb EncodeJob Object - /// </summary> - /// <param name="task"> - /// The task. - /// </param> - /// <param name="configuration"> - /// The configuration. - /// </param> 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<bool>(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<int>(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 } } - /// <summary> - /// Pause the currently running encode. - /// </summary> public void Pause() { if (this.instance != null) @@ -151,9 +143,6 @@ namespace HandBrakeWPF.Services.Encode } } - /// <summary> - /// Resume the currently running encode. - /// </summary> public void Resume() { if (this.instance != null) @@ -164,9 +153,6 @@ namespace HandBrakeWPF.Services.Encode } } - /// <summary> - /// Kill the process - /// </summary> 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. - - /// <summary> - /// Service Log Message. - /// </summary> - /// <param name="message">Log message content</param> 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)); } - /// <summary> - /// Encode Progress Event Handler - /// </summary> - /// <param name="sender"> - /// The sender. - /// </param> - /// <param name="e"> - /// The Interop.EncodeProgressEventArgs. - /// </param> private void InstanceEncodeProgress(object sender, EncodeProgressEventArgs e) { EventArgs.EncodeProgressEventArgs args = new EventArgs.EncodeProgressEventArgs @@ -238,16 +209,7 @@ namespace HandBrakeWPF.Services.Encode this.InvokeEncodeStatusChanged(args); } - - /// <summary> - /// Encode Completed Event Handler - /// </summary> - /// <param name="sender"> - /// The sender. - /// </param> - /// <param name="e"> - /// The e. - /// </param> + 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; /// <summary> /// The Log interface. /// </summary> - public interface ILog : ILogHandler + public interface ILog { /// <summary> /// The message logged. @@ -37,13 +34,13 @@ namespace HandBrakeWPF.Services.Logging.Interfaces /// <summary> /// Enable logging for this worker process. /// </summary> - /// <param name="config"> - /// Configuration for the logger. + /// <param name="filename"> + /// The filename. /// </param> /// <remarks> /// If this is not called, all log messages from libhb will be ignored. /// </remarks> - void ConfigureLogging(LogHandlerConfig config); + void ConfigureLogging(string filename); /// <summary> /// Log a message. @@ -52,5 +49,14 @@ namespace HandBrakeWPF.Services.Logging.Interfaces /// The content of the log message, /// </param> void LogMessage(string content); + + string GetFullLog(); + + List<LogMessage> GetLogMessages(); + + /// <summary> + /// Empty the log cache and reset the log handler to defaults. + /// </summary> + 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 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="ILogInstanceManager.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// Defines the ILogInstanceManager type. +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Logging.Interfaces +{ + using System; + using System.Collections.Generic; + + public interface ILogInstanceManager + { + event EventHandler NewLogInstanceRegistered; + + string ApplicationAndScanLog { get; } + + ILog MasterLogInstance { get; } + + /// <summary> + /// Register an ILog instance. + /// </summary> + /// <param name="filename"> + /// This is the key associated with the log instance. + /// </param> + /// <param name="log"> + /// The ILog instance + /// </param> + /// <param name="isMaster"> + /// True indicates it's the log instance for the parent handbrake process. + /// </param> + void RegisterLoggerInstance(string filename, ILog log, bool isMaster); + + /// <summary> + /// Gets a list of files without their associated ILog instances. + /// </summary> + /// <returns>List of filenames being logged</returns> + List<string> GetLogFiles(); + + /// <summary> + /// Get the ILog instance for a given filename key + /// </summary> + /// <param name="filename">The key of the log instance</param> + /// <returns>An ILog instance or null if invalid key</returns> + 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 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="LogInstanceManager.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +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<string, ILog> logInstances = new Dictionary<string, ILog>(); + + private int maxInstances; + + public LogInstanceManager(IUserSettingService userSettingService) + { + this.maxInstances = userSettingService.GetUserSetting<int>(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<string> 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<string> 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<LogMessage> logMessages = new List<LogMessage>(); + private bool isLoggingEnabled; - private List<LogMessage> logMessages = new List<LogMessage>(); 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<LogEventArgs> 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<bool>(UserSettingConstants.PreventSleep))
+ {
+ Win32.AllowSleep();
+ }
+ });
}
/// <summary>
@@ -90,15 +101,6 @@ namespace HandBrakeWPF.Services {
this.PlayWhenDoneSound();
}
-
- // Allow the system to sleep again.
- Execute.OnUIThread(() =>
- {
- if (this.userSettingService.GetUserSetting<bool>(UserSettingConstants.PreventSleep))
- {
- Win32.AllowSleep();
- }
- });
}
/// <summary>
@@ -172,6 +174,15 @@ namespace HandBrakeWPF.Services break;
}
}
+
+ // Allow the system to sleep again.
+ Execute.OnUIThread(() =>
+ {
+ if (this.userSettingService.GetUserSetting<bool>(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 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="ActiveJob.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// Defines the ActiveJob type. +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +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<ActiveJobCompletedEventArgs> JobFinished; + + public event EventHandler<EncodeProgressEventArgs> 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; /// <summary> /// The Queue Processor /// </summary> public interface IQueueService { - #region Events - /// <summary> /// Fires when the Queue has started /// </summary> - event QueueService.QueueProgressStatus JobProcessingStarted; + event EventHandler<QueueProgressEventArgs> JobProcessingStarted; + + /// <summary> + /// Fires when the status of any running job changes on the queue. Including progress. + /// </summary> + event EventHandler QueueJobStatusChanged; /// <summary> /// Fires when a job is Added, Removed or Re-Ordered. @@ -38,16 +43,14 @@ namespace HandBrakeWPF.Services.Queue.Interfaces /// <summary> /// Fires when the entire encode queue has completed. /// </summary> - event QueueService.QueueCompletedEventDelegate QueueCompleted; + event EventHandler<QueueCompletedEventArgs> QueueCompleted; /// <summary> /// Fires when a pause to the encode queue has been requested. /// </summary> event EventHandler QueuePaused; - #endregion - - #region Properties + event EventHandler<EncodeCompletedEventArgs> EncodeCompleted; /// <summary> /// Gets the number of jobs in the queue @@ -60,30 +63,19 @@ namespace HandBrakeWPF.Services.Queue.Interfaces int ErrorCount { get; } /// <summary> - /// Gets the IEncodeService instance. - /// </summary> - IEncode EncodeService { get; } - - /// <summary> /// Gets a value indicating whether IsProcessing. /// </summary> bool IsProcessing { get; } - /// <summary> - /// Gets or sets Last Processed Job. - /// This is set when the job is poped of the queue by GetNextJobForProcessing(); - /// </summary> - QueueTask LastProcessedJob { get; set; } + bool IsEncoding { get; } + + bool IsPaused { get; } /// <summary> /// Gets The current queue. /// </summary> ObservableCollection<QueueTask> Queue { get; } - #endregion - - #region Public Methods - /// <summary> /// 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. /// </summary> void Pause(); - + /// <summary> - /// Pause and Encode and the Queue. + /// Get the status of all running queue jobs. /// </summary> - void PauseEncode(); + /// <returns> + /// A list of QueueProgressStatus items + /// </returns> + List<QueueProgressStatus> GetQueueProgressStatus(); - #endregion + List<string> 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 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="ActiveJobCompletedEventArgs.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// -------------------------------------------------------------------------------------------------------------------- + +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 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="QueueProgressStatus.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// Defines the QueueProgressStatus type. +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +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);
}
}
@@ -94,6 +97,12 @@ namespace HandBrakeWPF.Services.Queue.Model public QueueStats Statistics { get; set; }
[JsonIgnore]
+ public QueueProgressStatus JobProgress { get; set; }
+
+ [JsonIgnore]
+ public bool IsJobStatusVisible => this.Status == QueueItemStatus.InProgress;
+
+ [JsonIgnore]
public string SelectedPresetKey
{
get
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<ActiveJob> activeJobs = new List<ActiveJob>(); private readonly IUserSettingService userSettingService; private readonly ILog logService; private readonly IErrorService errorService; + private readonly ILogInstanceManager logInstanceManager; + private readonly IHbFunctionsProvider hbFunctionsProvider; private readonly ObservableCollection<QueueTask> queue = new ObservableCollection<QueueTask>(); 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<int>(UserSettingConstants.SimultaneousEncodes); + } - public event QueueProgressStatus JobProcessingStarted; + public event EventHandler<QueueProgressEventArgs> JobProcessingStarted; public event EventHandler QueueChanged; - public event QueueCompletedEventDelegate QueueCompleted; + public event EventHandler<QueueCompletedEventArgs> QueueCompleted; public event EventHandler QueuePaused; + public event EventHandler QueueJobStatusChanged; + + public event EventHandler<EncodeCompletedEventArgs> 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<QueueTask> 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<QueueProgressStatus> GetQueueProgressStatus() { - QueueCompletedEventDelegate handler = this.QueueCompleted; - if (handler != null) + // TODO make thread safe. + List<QueueProgressStatus> statuses = new List<QueueProgressStatus>(); + 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<string> 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<string> directories = new List<string>(); + 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<bool>(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(job.Task.Destination, this.userSettingService.GetUserSetting<long>(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)); - } - } - - /// <summary> - /// For a given set of tasks, return the Queue JSON that can be used for the CLI. - /// </summary> - /// <param name="tasks"> - /// The tasks. - /// </param> - /// <param name="configuration"> - /// The configuration. - /// </param> - /// <returns> - /// The <see cref="string"/>. - /// </returns> + 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<EncodeTask> 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<bool>(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(job.Task.Destination, this.userSettingService.GetUserSetting<long>(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<long>(UserSettingConstants.PauseQueueOnLowDiskspaceLevel); if (!this.storageLowPause && this.userSettingService.GetUserSetting<bool>(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<bool>(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);
|