From 91051b41df7f9e6da68d14c9e806968df61ef050 Mon Sep 17 00:00:00 2001 From: Scott Date: Wed, 29 Apr 2020 19:06:11 +0100 Subject: WinGui: Enable multi-instance support. (#2797) --- .../HandBrake.Interop/Interop/HandBrakeInstance.cs | 12 ++-- .../Interop/Interfaces/IEncodeInstance.cs | 2 + .../Instance/HandBrakeInstanceManager.cs | 21 +++---- win/CS/HandBrakeWPF/Instance/RemoteInstance.cs | 60 +++++++++++++------- .../HandBrakeWPF/Properties/Resources.Designer.cs | 18 +++--- win/CS/HandBrakeWPF/Properties/Resources.de.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.es.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.fr.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.ja.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.ko.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.resx | 6 +- win/CS/HandBrakeWPF/Properties/Resources.ru.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.tr.resx | 3 - win/CS/HandBrakeWPF/Properties/Resources.zh.resx | 3 - win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs | 26 +++++---- .../Services/Logging/LogInstanceManager.cs | 64 ++++++++++++++------- .../Services/Queue/Interfaces/IQueueService.cs | 5 ++ win/CS/HandBrakeWPF/Services/Queue/QueueService.cs | 65 +++++++++++++--------- win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs | 37 +++++++----- win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs | 3 - .../ViewModels/StaticPreviewViewModel.cs | 64 ++++++--------------- win/CS/HandBrakeWPF/Views/MainView.xaml | 4 +- win/CS/HandBrakeWPF/Views/OptionsView.xaml | 3 +- win/CS/HandBrakeWPF/Views/ShellView.xaml | 4 +- win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml | 4 +- 25 files changed, 226 insertions(+), 196 deletions(-) (limited to 'win/CS') diff --git a/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs b/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs index a5027f72f..5d4dd98ac 100644 --- a/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs +++ b/win/CS/HandBrake.Interop/Interop/HandBrakeInstance.cs @@ -13,6 +13,7 @@ namespace HandBrake.Interop.Interop using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Timers; @@ -73,10 +74,6 @@ namespace HandBrake.Interop.Interop /// public event EventHandler EncodeCompleted; - /// - /// Gets the handle. - /// - internal IntPtr Handle { get; private set; } /// /// Gets the number of previews created during scan. @@ -108,6 +105,13 @@ namespace HandBrake.Interop.Interop /// public int Build => hbFunctions.hb_get_build(this.Handle); + public bool IsRemoteInstance => false; + + /// + /// Gets the handle. + /// + internal IntPtr Handle { get; private set; } + /// /// Initializes this instance. /// diff --git a/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs b/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs index 5a0957cb9..e7a52fbfa 100644 --- a/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs +++ b/win/CS/HandBrake.Interop/Interop/Interfaces/IEncodeInstance.cs @@ -27,6 +27,8 @@ namespace HandBrake.Interop.Interop.Interfaces /// event EventHandler EncodeProgress; + bool IsRemoteInstance { get; } + /// /// Initializes this instance. /// diff --git a/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs b/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs index a343a1afd..51ac11458 100644 --- a/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs +++ b/win/CS/HandBrakeWPF/Instance/HandBrakeInstanceManager.cs @@ -44,12 +44,6 @@ namespace HandBrakeWPF.Instance throw new Exception("Please call Init before Using!"); } - if (encodeInstance != null) - { - encodeInstance.Dispose(); - encodeInstance = null; - } - IEncodeInstance newInstance; if (userSettingService.GetUserSetting(UserSettingConstants.ProcessIsolationEnabled) && Portable.IsProcessIsolationEnabled()) @@ -58,16 +52,19 @@ namespace HandBrakeWPF.Instance } else { + if (encodeInstance != null && !encodeInstance.IsRemoteInstance) + { + encodeInstance.Dispose(); + encodeInstance = null; + } + newInstance = new HandBrakeInstance(); + HandBrakeUtils.SetDvdNav(!userSettingService.GetUserSetting(UserSettingConstants.DisableLibDvdNav)); + encodeInstance = newInstance; } newInstance.Initialize(verbosity, noHardware); - - encodeInstance = newInstance; - - HandBrakeUtils.SetDvdNav(!userSettingService.GetUserSetting(UserSettingConstants.DisableLibDvdNav)); - - return encodeInstance; + return newInstance; } /// diff --git a/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs b/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs index c2bae9c85..97abf671b 100644 --- a/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs +++ b/win/CS/HandBrakeWPF/Instance/RemoteInstance.cs @@ -31,6 +31,8 @@ namespace HandBrakeWPF.Instance using HandBrake.Worker.Routing.Commands; using HandBrakeWPF.Instance.Model; + using HandBrakeWPF.Model.Options; + using HandBrakeWPF.Services; using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.Services.Logging.Interfaces; using HandBrakeWPF.Utilities; @@ -62,6 +64,8 @@ namespace HandBrakeWPF.Instance public event EventHandler EncodeProgress; + public bool IsRemoteInstance => true; + public async void PauseEncode() { await this.MakeHttpGetRequest("PauseEncode"); @@ -74,13 +78,13 @@ namespace HandBrakeWPF.Instance this.MonitorEncodeProgress(); } - public async void StartEncode(JsonEncodeObject jobToStart) + public void StartEncode(JsonEncodeObject jobToStart) { InitCommand initCommand = new InitCommand { EnableDiskLogging = false, AllowDisconnectedWorker = false, - DisableLibDvdNav = this.userSettingService.GetUserSetting(UserSettingConstants.DisableLibDvdNav), + DisableLibDvdNav = !this.userSettingService.GetUserSetting(UserSettingConstants.DisableLibDvdNav), EnableHardwareAcceleration = true, LogDirectory = DirectoryUtilities.GetLogDirectory(), LogVerbosity = this.userSettingService.GetUserSetting(UserSettingConstants.Verbosity) @@ -91,7 +95,8 @@ namespace HandBrakeWPF.Instance JsonSerializerSettings settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; string job = JsonConvert.SerializeObject(new EncodeCommand { InitialiseCommand = initCommand, EncodeJob = jobToStart }, Formatting.None, settings); - await this.MakeHttpJsonPostRequest("StartEncode", job); + var task = Task.Run(async () => await this.MakeHttpJsonPostRequest("StartEncode", job)); + task.Wait(); this.MonitorEncodeProgress(); } @@ -118,16 +123,6 @@ namespace HandBrakeWPF.Instance } public void Initialize(int verbosityLvl, bool noHardwareMode) - { - this.StartServer(); - } - - public void Dispose() - { - this.workerProcess?.Dispose(); - } - - private async void StartServer() { if (this.workerProcess == null || this.workerProcess.HasExited) { @@ -135,8 +130,8 @@ namespace HandBrakeWPF.Instance this.base64Token = Convert.ToBase64String(plainTextBytes); workerProcess = new Process - { - StartInfo = + { + StartInfo = { FileName = "HandBrake.Worker.exe", Arguments = string.Format(" --port={0} --token={1}", port, this.base64Token), @@ -145,20 +140,44 @@ namespace HandBrakeWPF.Instance RedirectStandardError = true, CreateNoWindow = true } - }; + }; workerProcess.Exited += this.WorkerProcess_Exited; workerProcess.OutputDataReceived += this.WorkerProcess_OutputDataReceived; workerProcess.ErrorDataReceived += this.WorkerProcess_OutputDataReceived; - + workerProcess.Start(); workerProcess.BeginOutputReadLine(); workerProcess.BeginErrorReadLine(); + // Set Process Priority + switch ((ProcessPriority)this.userSettingService.GetUserSetting(UserSettingConstants.ProcessPriorityInt)) + { + case ProcessPriority.High: + workerProcess.PriorityClass = ProcessPriorityClass.High; + break; + case ProcessPriority.AboveNormal: + workerProcess.PriorityClass = ProcessPriorityClass.AboveNormal; + break; + case ProcessPriority.Normal: + workerProcess.PriorityClass = ProcessPriorityClass.Normal; + break; + case ProcessPriority.Low: + workerProcess.PriorityClass = ProcessPriorityClass.Idle; + break; + default: + workerProcess.PriorityClass = ProcessPriorityClass.BelowNormal; + break; + } this.logService.LogMessage(string.Format("Worker Process started with Process ID: {0} and port: {1}", this.workerProcess.Id, port)); } } + public void Dispose() + { + this.workerProcess?.Dispose(); + } + private void WorkerProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) { this.logService.LogMessage(e.Data); @@ -213,7 +232,10 @@ namespace HandBrakeWPF.Instance this.encodePollTimer?.Stop(); - this.workerProcess?.Kill(); + if (this.workerProcess != null && !this.workerProcess.HasExited) + { + this.workerProcess?.Kill(); + } return; } @@ -259,7 +281,7 @@ namespace HandBrakeWPF.Instance else if (taskState != null && taskState == TaskState.WorkDone) { this.encodePollTimer.Stop(); - if (!this.workerProcess.HasExited) + if (this.workerProcess != null && !this.workerProcess.HasExited) { this.workerProcess?.Kill(); } diff --git a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs index 00d0044cd..876e5f43a 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs +++ b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs @@ -4915,15 +4915,6 @@ namespace HandBrakeWPF.Properties { } } - /// - /// Looks up a localized string similar to The Queue has been paused. The currently running job will run to completion and no further jobs will start.. - /// - public static string QueueViewModel_QueuePauseNotice { - get { - return ResourceManager.GetString("QueueViewModel_QueuePauseNotice", resourceCulture); - } - } - /// /// Looks up a localized string similar to Queue Paused. /// @@ -5249,6 +5240,15 @@ namespace HandBrakeWPF.Properties { } } + /// + /// Looks up a localized string similar to Cancel. + /// + public static string StaticPreviewView_CancelPreview { + get { + return ResourceManager.GetString("StaticPreviewView_CancelPreview", resourceCulture); + } + } + /// /// Looks up a localized string similar to Duration:. /// diff --git a/win/CS/HandBrakeWPF/Properties/Resources.de.resx b/win/CS/HandBrakeWPF/Properties/Resources.de.resx index 69c9f01ff..a3f22724c 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.de.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.de.resx @@ -501,9 +501,6 @@ Bitte die Webseite auf Versionshinweise prüfen. Warteschlange angehalten - - Die Warteschlange wurde angehalten. Die aktuelle Aufgabe wird noch fertig gestellt und danach keine weiteren Aufgaben gestartet. - Sollen die ausgewählten Aufgaben wirklich gelöscht werden? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.es.resx b/win/CS/HandBrakeWPF/Properties/Resources.es.resx index 8ad365676..eb0befdef 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.es.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.es.resx @@ -497,9 +497,6 @@ Puede encontrar más información en el registro de actividad. Cola pausada - - La cola ha sido pausada. El trabajo activo terminará pero no se iniciaran nuevos. - ¿Está seguro que desea borrar los trabajos seleccionados? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.fr.resx b/win/CS/HandBrakeWPF/Properties/Resources.fr.resx index 74d1af845..da93b5d98 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.fr.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.fr.resx @@ -498,9 +498,6 @@ Veuillez consulter le site Web pour les notes de version. File mise en pause - - La file a été mise en pause. Le travail en cours sera exécuté en totalité et aucun autre travail ne commencera. - Êtes-vous sûr de vouloir supprimer les travaux sélectionnés ? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.ja.resx b/win/CS/HandBrakeWPF/Properties/Resources.ja.resx index d42b49f19..9566dbc84 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.ja.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.ja.resx @@ -497,9 +497,6 @@ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 021 キュー一時停止 - - キューが一時停止されました。現在のジョブは完了まで実行され、それ以降のジョブは開始しません。 - 選択したジョブを本当に削除しますか? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.ko.resx b/win/CS/HandBrakeWPF/Properties/Resources.ko.resx index 977dbcb45..5704eeebd 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.ko.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.ko.resx @@ -497,9 +497,6 @@ Activity log에 추가 정보가 있습니다. 대기열 일시 중지 - - 대기열이 일시 중지되었습니다. 현재 실행 중인 작업은 완료될 때까지 실행되며 추가 작업은 실행되지 않습니다. - 선택된 작업을 삭제하시겠습니까? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.resx b/win/CS/HandBrakeWPF/Properties/Resources.resx index 7ba7be44a..de06cc56c 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.resx @@ -498,9 +498,6 @@ The Activity log may have further information. Queue Paused - - The Queue has been paused. The currently running job will run to completion and no further jobs will start. - Are you sure you want to delete the selected jobs? @@ -2229,4 +2226,7 @@ Please choose a different preset. Number of simultaneous encodes: + + Cancel + \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Properties/Resources.ru.resx b/win/CS/HandBrakeWPF/Properties/Resources.ru.resx index eaa4d4317..f1f709888 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.ru.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.ru.resx @@ -500,9 +500,6 @@ Foreign Audio Preferred, else First - Если существует дорожк Очередь приостановлена - - Очередь была приостановлена. Текущее задание будет выполнено, а будущие - не начнутся. - Вы уверены, что хотите удалить выбранные задания? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.tr.resx b/win/CS/HandBrakeWPF/Properties/Resources.tr.resx index 42757b74b..0102da37f 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.tr.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.tr.resx @@ -500,9 +500,6 @@ Faaliyet günlüğü daha fazla bilgi içerebilir. Sıra Duraklatıldı - - Sıra duraklatıldı. Halen çalışmakta olan iş tamamlanmaya devam eder ve başka iş başlatılmaz. - Seçilen işleri silmek istediğinizden emin misiniz? diff --git a/win/CS/HandBrakeWPF/Properties/Resources.zh.resx b/win/CS/HandBrakeWPF/Properties/Resources.zh.resx index 189616dbe..213ff6e0d 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.zh.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.zh.resx @@ -493,9 +493,6 @@ Foreign Audio Preferred, else First - 如果存在外语轨道,则会将其烧 列队已暂停 - - 队列已暂停。当前正在运行的作业将运行完成,不会再启动其他作业。 - 您确定要删除所选作业吗? diff --git a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs index e6d8cb7c0..fdd2a01e5 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs @@ -40,6 +40,7 @@ namespace HandBrakeWPF.Services.Encode private readonly IUserSettingService userSettingService; private readonly ILogInstanceManager logInstanceManager; private readonly IHbFunctionsProvider hbFunctionsProvider; + private readonly object portLock = new object(); private IEncodeInstance instance; private DateTime startTime; private EncodeTask currentTask; @@ -107,19 +108,24 @@ 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.encodeLogService, userSettingService); - - this.instance.EncodeCompleted += this.InstanceEncodeCompleted; - this.instance.EncodeProgress += this.InstanceEncodeProgress; - this.IsEncoding = true; - this.isPreviewInstance = task.IsPreviewEncode; + // Prevent port stealing if multiple jobs start at the same time. + lock (portLock) + { + 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; - // Verify the Destination Path Exists, and if not, create it. - this.VerifyEncodeDestinationPath(task); + this.IsEncoding = true; + this.isPreviewInstance = task.IsPreviewEncode; - // Get an EncodeJob object for the Interop Library - this.instance.StartEncode(EncodeTaskFactory.Create(task, configuration, hbFunctionsProvider.GetHbFunctionsWrapper())); + // Verify the Destination Path Exists, and if not, create it. + this.VerifyEncodeDestinationPath(task); + + // Get an EncodeJob object for the Interop Library + this.instance.StartEncode(EncodeTaskFactory.Create(task, configuration, hbFunctionsProvider.GetHbFunctionsWrapper())); + } // Fire the Encode Started Event this.InvokeEncodeStarted(System.EventArgs.Empty); diff --git a/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs b/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs index 4e1290024..16c9aa7dc 100644 --- a/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs +++ b/win/CS/HandBrakeWPF/Services/Logging/LogInstanceManager.cs @@ -19,13 +19,26 @@ namespace HandBrakeWPF.Services.Logging public class LogInstanceManager : ILogInstanceManager { - private Dictionary logInstances = new Dictionary(); + private readonly IUserSettingService userSettingService; + private readonly object instanceLock = new object(); + private Dictionary logInstances = new Dictionary(); + private int maxInstances; public LogInstanceManager(IUserSettingService userSettingService) { - this.maxInstances = userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + this.userSettingService = userSettingService; + this.maxInstances = this.userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + userSettingService.SettingChanged += this.UserSettingService_SettingChanged; + } + + private void UserSettingService_SettingChanged(object sender, HandBrakeWPF.EventArgs.SettingChangedEventArgs e) + { + if (e.Key == UserSettingConstants.SimultaneousEncodes) + { + this.maxInstances = this.userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + } } public event EventHandler NewLogInstanceRegistered; @@ -36,19 +49,22 @@ namespace HandBrakeWPF.Services.Logging public void RegisterLoggerInstance(string filename, ILog log, bool isMaster) { - if (string.IsNullOrEmpty(this.ApplicationAndScanLog)) + lock (this.instanceLock) { - // The application startup sets the initial log file. - this.ApplicationAndScanLog = filename; - } + if (string.IsNullOrEmpty(this.ApplicationAndScanLog)) + { + // The application startup sets the initial log file. + this.ApplicationAndScanLog = filename; + } - this.logInstances.Add(filename, log); + this.logInstances.Add(filename, log); - this.CleanupInstance(); + this.CleanupInstance(); - if (isMaster) - { - this.MasterLogInstance = log; + if (isMaster) + { + this.MasterLogInstance = log; + } } this.OnNewLogInstanceRegistered(); @@ -56,20 +72,26 @@ namespace HandBrakeWPF.Services.Logging public List GetLogFiles() { - return this.logInstances.Keys.ToList(); + lock (this.instanceLock) + { + return this.logInstances.Keys.ToList(); + } } public ILog GetLogInstance(string filename) { - if (string.IsNullOrEmpty(filename)) + lock (this.instanceLock) { - return null; - } - - ILog logger; - if (this.logInstances.TryGetValue(filename, out logger)) - { - return logger; + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + ILog logger; + if (this.logInstances.TryGetValue(filename, out logger)) + { + return logger; + } } return null; @@ -87,7 +109,7 @@ namespace HandBrakeWPF.Services.Logging if (encodeLogs.Count > this.maxInstances) { this.logInstances.Remove(removalKey); - } + } } } } diff --git a/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs index 1a2a04cce..5bf759917 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/Interfaces/IQueueService.cs @@ -62,6 +62,11 @@ namespace HandBrakeWPF.Services.Queue.Interfaces /// int ErrorCount { get; } + /// + /// Gets the number of completed jobs. + /// + int CompletedCount { get; } + /// /// Gets a value indicating whether IsProcessing. /// diff --git a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs index 6cbf709e5..41cba661f 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs @@ -59,9 +59,12 @@ namespace HandBrakeWPF.Services.Queue private readonly ObservableCollection queue = new ObservableCollection(); private readonly string queueFile; + private readonly object queueFileLock = new object(); + private bool clearCompleted; private int allowedInstances; private int jobIdCounter = 0; + private bool processIsolationEnabled; public QueueService(IUserSettingService userSettingService, ILog logService, IErrorService errorService, ILogInstanceManager logInstanceManager, IHbFunctionsProvider hbFunctionsProvider) { @@ -75,6 +78,7 @@ namespace HandBrakeWPF.Services.Queue this.queueFile = string.Format("{0}{1}.json", QueueRecoveryHelper.QueueFileName, GeneralUtilities.ProcessId); this.allowedInstances = this.userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + this.processIsolationEnabled = this.userSettingService.GetUserSetting(UserSettingConstants.ProcessIsolationEnabled); } public event EventHandler JobProcessingStarted; @@ -105,6 +109,8 @@ namespace HandBrakeWPF.Services.Queue } } + public int CompletedCount => this.queue.Count(item => item.Status == QueueItemStatus.Completed); + public bool IsPaused { get; private set; } public bool IsProcessing { get; private set; } @@ -130,33 +136,31 @@ namespace HandBrakeWPF.Services.Queue public void BackupQueue(string exportPath) { - Stopwatch watch = Stopwatch.StartNew(); - - string appDataPath = DirectoryUtilities.GetUserStoragePath(VersionHelper.IsNightly()); - string tempPath = !string.IsNullOrEmpty(exportPath) - ? exportPath - : Path.Combine(appDataPath, string.Format(this.queueFile, string.Empty)); - - // Make a copy of the file before we replace it. This way, if we crash we can recover. - if (File.Exists(tempPath)) + lock (this.queueFileLock) { - File.Copy(tempPath, tempPath + ".last"); - } + string appDataPath = DirectoryUtilities.GetUserStoragePath(VersionHelper.IsNightly()); + string tempPath = !string.IsNullOrEmpty(exportPath) + ? exportPath + : Path.Combine(appDataPath, string.Format(this.queueFile, string.Empty)); - using (StreamWriter writer = new StreamWriter(tempPath)) - { - List tasks = this.queue.Where(item => item.Status != QueueItemStatus.Completed).ToList(); - string queueJson = JsonConvert.SerializeObject(tasks, Formatting.Indented); - writer.Write(queueJson); - } + // Make a copy of the file before we replace it. This way, if we crash we can recover. + if (File.Exists(tempPath)) + { + File.Copy(tempPath, tempPath + ".last"); + } - if (File.Exists(tempPath + ".last")) - { - File.Delete(tempPath + ".last"); - } + using (StreamWriter writer = new StreamWriter(tempPath)) + { + List tasks = this.queue.Where(item => item.Status != QueueItemStatus.Completed).ToList(); + string queueJson = JsonConvert.SerializeObject(tasks, Formatting.Indented); + writer.Write(queueJson); + } - watch.Stop(); - Debug.WriteLine("Queue Save (ms): " + watch.ElapsedMilliseconds); + if (File.Exists(tempPath + ".last")) + { + File.Delete(tempPath + ".last"); + } + } } public void ExportCliJson(string exportPath) @@ -447,6 +451,9 @@ namespace HandBrakeWPF.Services.Queue this.IsPaused = false; this.clearCompleted = isClearCompleted; + this.allowedInstances = this.userSettingService.GetUserSetting(UserSettingConstants.SimultaneousEncodes); + this.processIsolationEnabled = this.userSettingService.GetUserSetting(UserSettingConstants.ProcessIsolationEnabled); + // Unpause all active jobs. foreach (ActiveJob job in this.activeJobs) { @@ -523,6 +530,11 @@ namespace HandBrakeWPF.Services.Queue private void ProcessNextJob() { + if (!this.processIsolationEnabled) + { + this.allowedInstances = 1; + } + if (this.activeJobs.Count >= this.allowedInstances) { return; @@ -555,8 +567,11 @@ namespace HandBrakeWPF.Services.Queue { this.BackupQueue(string.Empty); - // Fire the event to tell connected services. - this.InvokeQueueCompleted(new QueueCompletedEventArgs(false)); + if (!this.activeJobs.Any(a => a.IsEncoding)) + { + // Fire the event to tell connected services. + this.InvokeQueueCompleted(new QueueCompletedEventArgs(false)); + } } } diff --git a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs index f88adaded..c1124817e 100644 --- a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs @@ -62,8 +62,6 @@ namespace HandBrakeWPF.ViewModels /// public class MainViewModel : ViewModelBase, IMainViewModel { - #region Private Variables and Services - private readonly IQueueService queueProcessor; private readonly IPresetService presetService; private readonly IErrorService errorService; @@ -96,13 +94,6 @@ namespace HandBrakeWPF.ViewModels private bool isModifiedPreset; private bool updateAvailable; - #endregion - - /// - /// Initializes a new instance of the class. - /// The viewmodel for HandBrakes main window. - /// - /// whenDoneService must be a serivce here! public MainViewModel( IUserSettingService userSettingService, IScan scanService, @@ -1070,6 +1061,8 @@ namespace HandBrakeWPF.ViewModels } } + public bool IsMultiProcess { get; set; } + #endregion #region Commands @@ -2495,6 +2488,7 @@ namespace HandBrakeWPF.ViewModels private void QueueCompleted(object sender, EventArgs e) { this.NotifyOfPropertyChange(() => this.IsEncoding); + this.NotifyOfPropertyChange(() => this.StartLabel); Execute.OnUIThread( () => @@ -2530,7 +2524,11 @@ namespace HandBrakeWPF.ViewModels Execute.OnUIThread( () => { - this.ProgramStatusLabel = string.Format(Resources.Main_XEncodesPending, this.queueProcessor.Count); + if (!this.queueProcessor.IsEncoding) + { + this.ProgramStatusLabel = string.Format(Resources.Main_XEncodesPending, this.queueProcessor.Count); + } + this.NotifyOfPropertyChange(() => this.QueueLabel); this.NotifyOfPropertyChange(() => this.StartLabel); this.NotifyOfPropertyChange(() => this.IsEncoding); @@ -2545,15 +2543,20 @@ namespace HandBrakeWPF.ViewModels this.ProgramStatusLabel = Resources.Main_QueuePaused; this.NotifyOfPropertyChange(() => this.QueueLabel); this.NotifyOfPropertyChange(() => this.StartLabel); + this.NotifyOfPropertyChange(() => this.IsEncoding); }); } - - + private void QueueProcessor_QueueJobStatusChanged(object sender, EventArgs e) { List queueJobStatuses = this.queueProcessor.GetQueueProgressStatus(); string jobsPending = string.Format(Resources.Main_JobsPending_addon, this.queueProcessor.Count); + if (this.queueProcessor.IsPaused) + { + return; + } + Execute.OnUIThread( () => { @@ -2579,10 +2582,15 @@ namespace HandBrakeWPF.ViewModels this.WindowTitle = string.Format(Resources.WindowTitleStatus, Resources.HandBrake_Title, this.ProgressPercentage, status.Task, status.TaskCount); this.notifyIconService.SetTooltip(string.Format(Resources.TaskTrayStatusTitle, Resources.HandBrake_Title, this.ProgressPercentage, status.Task, status.TaskCount, status.EstimatedTimeLeft)); } + + this.IsMultiProcess = false; + this.NotifyOfPropertyChange(() => this.IsMultiProcess); } else if (queueJobStatuses.Count > 1) { - this.ProgramStatusLabel = "Multiple Jobs Running."; // TODO Implement later. + this.ProgramStatusLabel = string.Format("{0} jobs completed. {1}Working on {2} jobs with {3} waiting to be processed.", this.queueProcessor.CompletedCount, Environment.NewLine, queueJobStatuses.Count, this.queueProcessor.Count); + this.IsMultiProcess = true; + this.NotifyOfPropertyChange(() => this.IsMultiProcess); } else { @@ -2591,6 +2599,9 @@ namespace HandBrakeWPF.ViewModels this.WindowTitle = Resources.HandBrake_Title; this.notifyIconService.SetTooltip(this.WindowTitle); + this.IsMultiProcess = false; + this.NotifyOfPropertyChange(() => this.IsMultiProcess); + if (this.windowsSeven.IsWindowsSeven) { this.windowsSeven.SetTaskBarProgressToNoProgress(); diff --git a/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs index 1d3650264..2926e40e5 100644 --- a/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs @@ -231,9 +231,6 @@ namespace HandBrakeWPF.ViewModels this.JobsPending = string.Format(Resources.QueueViewModel_JobsPending, this.queueProcessor.Count); this.IsQueueRunning = false; - - this.errorService.ShowMessageBox(Resources.QueueViewModel_QueuePauseNotice, Resources.QueueViewModel_Queue, - MessageBoxButton.OK, MessageBoxImage.Information); } public void PauseQueueToolbar() diff --git a/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs index 996d93949..52d4bb4b3 100644 --- a/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs @@ -41,9 +41,6 @@ namespace HandBrakeWPF.ViewModels using OutputFormat = HandBrakeWPF.Services.Encode.Model.Models.OutputFormat; using PointToPointMode = HandBrakeWPF.Services.Encode.Model.Models.PointToPointMode; - /// - /// The Static Preview View Model - /// public class StaticPreviewViewModel : ViewModelBase, IStaticPreviewViewModel { private readonly IScan scanService; @@ -61,8 +58,6 @@ namespace HandBrakeWPF.ViewModels private bool useSystemDefaultPlayer; private bool previewRotateFlip; - #region Constructors and Destructors - public StaticPreviewViewModel(IScan scanService, IUserSettingService userSettingService, IErrorService errorService, IHbFunctionsProvider hbFunctionsProvider, ILog logService, ILogInstanceManager logInstanceManager) { this.scanService = scanService; @@ -86,11 +81,7 @@ namespace HandBrakeWPF.ViewModels this.previewRotateFlip = userSettingService.GetUserSetting(UserSettingConstants.PreviewRotationFlip); this.NotifyOfPropertyChange(() => this.previewRotateFlip); // Don't want to trigger an Update, so setting the backing variable. } - - #endregion - - #region Public Properties - + /// /// Gets or sets the height. /// @@ -233,10 +224,6 @@ namespace HandBrakeWPF.ViewModels } } - #endregion - - #region LivePreviewProperties - /// /// Gets AvailableDurations. /// @@ -350,9 +337,8 @@ namespace HandBrakeWPF.ViewModels /// Gets or sets a value indicating whether can play. /// public bool CanPlay { get; set; } - #endregion - #region Public Methods and Operators + public bool IsOpen { get; set; } /// /// The update preview frame. @@ -372,11 +358,6 @@ namespace HandBrakeWPF.ViewModels this.ScannedSource = scannedSource; } - /// - /// Gets or sets a value indicating whether is open. - /// - public bool IsOpen { get; set; } - public void NextPreview() { int maxPreview = this.userSettingService.GetUserSetting(UserSettingConstants.PreviewScanCount); @@ -442,12 +423,6 @@ namespace HandBrakeWPF.ViewModels } } - /// - /// The preview size changed. - /// - /// - /// The ea. - /// public int FixWidth(int width) { Rect workArea = SystemParameters.WorkArea; @@ -470,12 +445,6 @@ namespace HandBrakeWPF.ViewModels return height; } - #endregion - - #region Public Method - Live Preview - - #region Public Methods - /// /// Close this window. /// @@ -500,19 +469,19 @@ namespace HandBrakeWPF.ViewModels { this.IsEncoding = true; if (File.Exists(this.CurrentlyPlaying)) + { File.Delete(this.CurrentlyPlaying); + } } catch (Exception) { this.IsEncoding = false; - this.errorService.ShowMessageBox(Resources.StaticPreview_UnableToDeletePreview, - Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); + this.errorService.ShowMessageBox(Resources.StaticPreview_UnableToDeletePreview, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); } if (this.Task == null || string.IsNullOrEmpty(Task.Source)) { - this.errorService.ShowMessageBox(Resources.StaticPreviewViewModel_ScanFirst, - Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); + this.errorService.ShowMessageBox(Resources.StaticPreviewViewModel_ScanFirst, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); return; } @@ -540,6 +509,7 @@ namespace HandBrakeWPF.ViewModels formatExtension = "mkv"; break; } + string filename = Path.ChangeExtension(Path.GetTempFileName(), formatExtension); encodeTask.Destination = filename; this.CurrentlyPlaying = filename; @@ -579,9 +549,14 @@ namespace HandBrakeWPF.ViewModels ThreadPool.QueueUserWorkItem(this.CreatePreview, task); } - #endregion + public void CancelEncode() + { + if (this.encodeService.IsEncoding) + { + this.encodeService.Stop(); + } + } - #region Private Methods /// /// Play the Encoded file @@ -665,10 +640,6 @@ namespace HandBrakeWPF.ViewModels this.userSettingService.SetUserSetting(UserSettingConstants.LastPreviewDuration, this.Duration); } - #endregion - - #region Event Handlers - /// /// Handle Encode Progress Events /// @@ -702,9 +673,10 @@ namespace HandBrakeWPF.ViewModels this.encodeService.EncodeCompleted -= this.encodeService_EncodeCompleted; this.encodeService.EncodeStatusChanged -= this.encodeService_EncodeStatusChanged; - this.PlayFile(); + if (e.ErrorInformation != "1") + { + this.PlayFile(); + } } - #endregion - #endregion } } \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Views/MainView.xaml b/win/CS/HandBrakeWPF/Views/MainView.xaml index 10712ac05..df77fc923 100644 --- a/win/CS/HandBrakeWPF/Views/MainView.xaml +++ b/win/CS/HandBrakeWPF/Views/MainView.xaml @@ -797,10 +797,10 @@ - + - diff --git a/win/CS/HandBrakeWPF/Views/OptionsView.xaml b/win/CS/HandBrakeWPF/Views/OptionsView.xaml index 67d86bdee..baaadb5e5 100644 --- a/win/CS/HandBrakeWPF/Views/OptionsView.xaml +++ b/win/CS/HandBrakeWPF/Views/OptionsView.xaml @@ -416,8 +416,7 @@ - - + diff --git a/win/CS/HandBrakeWPF/Views/ShellView.xaml b/win/CS/HandBrakeWPF/Views/ShellView.xaml index e7a6feba6..dfa3f6f24 100644 --- a/win/CS/HandBrakeWPF/Views/ShellView.xaml +++ b/win/CS/HandBrakeWPF/Views/ShellView.xaml @@ -7,9 +7,9 @@ xmlns:cal="http://www.caliburnproject.org" Title="{Data:Binding Path=MainViewModel.WindowTitle}" Width="1018" - Height="650" + Height="660" MinWidth="1018" - MinHeight="650" + MinHeight="660" AllowDrop="True" SnapsToDevicePixels="True" UseLayoutRounding="True" diff --git a/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml b/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml index db622c247..584270569 100644 --- a/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml +++ b/win/CS/HandBrakeWPF/Views/StaticPreviewView.xaml @@ -43,7 +43,9 @@ -