summaryrefslogtreecommitdiffstats
path: root/win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs')
-rw-r--r--win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs555
1 files changed, 446 insertions, 109 deletions
diff --git a/win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs b/win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs
index 39f31a44c..0b3290839 100644
--- a/win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs
+++ b/win/CS/HandBrake.ApplicationServices/Services/QueueProcessor.cs
@@ -10,10 +10,18 @@
namespace HandBrake.ApplicationServices.Services
{
using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
using System.Windows.Forms;
+ using System.Xml.Serialization;
+
+ using Caliburn.Micro;
using HandBrake.ApplicationServices.EventArgs;
+ using HandBrake.ApplicationServices.Exceptions;
using HandBrake.ApplicationServices.Model;
using HandBrake.ApplicationServices.Services.Interfaces;
using HandBrake.ApplicationServices.Utilities;
@@ -23,17 +31,35 @@ namespace HandBrake.ApplicationServices.Services
/// </summary>
public class QueueProcessor : IQueueProcessor
{
+ #region Constants and Fields
+
+ /// <summary>
+ /// A Lock object to maintain thread safety
+ /// </summary>
+ private static readonly object QueueLock = new object();
+
+ /// <summary>
+ /// The Queue of Job objects
+ /// </summary>
+ private readonly ObservableCollection<QueueTask> queue = new ObservableCollection<QueueTask>();
+
/// <summary>
/// The User Setting Service
/// </summary>
private readonly IUserSettingService userSettingService;
/// <summary>
+ /// HandBrakes Queue file with a place holder for an extra string.
+ /// </summary>
+ private string queueFile;
+
+ #endregion
+
+ #region Constructors and Destructors
+
+ /// <summary>
/// Initializes a new instance of the <see cref="QueueProcessor"/> class.
/// </summary>
- /// <param name="queueManager">
- /// The queue manager.
- /// </param>
/// <param name="encodeService">
/// The encode Service.
/// </param>
@@ -43,24 +69,18 @@ namespace HandBrake.ApplicationServices.Services
/// <exception cref="ArgumentNullException">
/// Services are not setup
/// </exception>
- public QueueProcessor(IQueueManager queueManager, IEncodeServiceWrapper encodeService, IUserSettingService userSettingService)
+ public QueueProcessor(IEncodeServiceWrapper encodeService, IUserSettingService userSettingService)
{
this.userSettingService = userSettingService;
- this.QueueManager = queueManager;
this.EncodeService = encodeService;
- if (this.QueueManager == null)
- {
- throw new ArgumentNullException("queueManager");
- }
-
- if (this.QueueManager == null)
- {
- throw new ArgumentNullException("queueManager");
- }
+ // If this is the first instance, just use the main queue file, otherwise add the instance id to the filename.
+ this.queueFile = string.Format("hb_queue_recovery{0}.xml", GeneralUtilities.GetInstanceCount);
}
- #region Events
+ #endregion
+
+ #region Delegates
/// <summary>
/// Queue Progess Status
@@ -73,15 +93,20 @@ namespace HandBrake.ApplicationServices.Services
/// </param>
public delegate void QueueProgressStatus(object sender, QueueProgressEventArgs e);
+ #endregion
+
+ #region Events
+
/// <summary>
/// Fires when the Queue has started
/// </summary>
public event QueueProgressStatus JobProcessingStarted;
/// <summary>
- /// Fires when a pause to the encode queue has been requested.
+ /// Fires when a job is Added, Removed or Re-Ordered.
+ /// Should be used for triggering an update of the Queue Window.
/// </summary>
- public event EventHandler QueuePaused;
+ public event EventHandler QueueChanged;
/// <summary>
/// Fires when the entire encode queue has completed.
@@ -89,87 +114,312 @@ namespace HandBrake.ApplicationServices.Services
public event EventHandler QueueCompleted;
/// <summary>
- /// Invoke the JobProcessingStarted event
+ /// Fires when a pause to the encode queue has been requested.
/// </summary>
- /// <param name="e">
- /// The QueueProgressEventArgs.
+ public event EventHandler QueuePaused;
+
+ #endregion
+
+ #region Properties
+
+ /// <summary>
+ /// Gets the number of jobs in the queue;
+ /// </summary>
+ public int Count
+ {
+ get
+ {
+ return this.queue.Count(item => item.Status == QueueItemStatus.Waiting);
+ }
+ }
+
+ /// <summary>
+ /// Gets the IEncodeService instance.
+ /// </summary>
+ public IEncodeServiceWrapper EncodeService { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether IsProcessing.
+ /// </summary>
+ public bool IsProcessing { get; private set; }
+
+ /// <summary>
+ /// Gets or sets Last Processed Job.
+ /// This is set when the job is poped of the queue by GetNextJobForProcessing();
+ /// </summary>
+ public QueueTask LastProcessedJob { get; set; }
+
+ /// <summary>
+ /// Gets The current queue.
+ /// </summary>
+ public ObservableCollection<QueueTask> Queue
+ {
+ get
+ {
+ return this.queue;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Add a job to the Queue.
+ /// This method is Thread Safe.
+ /// </summary>
+ /// <param name="job">
+ /// The encode Job object.
/// </param>
- private void InvokeJobProcessingStarted(QueueProgressEventArgs e)
+ public void Add(QueueTask job)
{
- QueueProgressStatus handler = this.JobProcessingStarted;
- if (handler != null)
+ lock (QueueLock)
{
- handler(this, e);
+ this.queue.Add(job);
+ this.InvokeQueueChanged(EventArgs.Empty);
}
}
/// <summary>
- /// Invoke the QueuePaused event
+ /// Backup any changes to the queue file
/// </summary>
- /// <param name="e">
- /// The EventArgs.
+ /// <param name="exportPath">
+ /// If this is not null or empty, this will be used instead of the standard backup location.
/// </param>
- private void InvokeQueuePaused(EventArgs e)
+ public void BackupQueue(string exportPath)
{
- EventHandler handler = this.QueuePaused;
- if (handler != null)
+ string appDataPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"HandBrake\");
+ string tempPath = !string.IsNullOrEmpty(exportPath)
+ ? exportPath
+ : appDataPath + string.Format(this.queueFile, string.Empty);
+
+ using (var strm = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
{
- handler(this, e);
+ List<QueueTask> tasks = this.queue.Where(item => item.Status != QueueItemStatus.Completed).ToList();
+ var serializer = new XmlSerializer(typeof(List<QueueTask>));
+ serializer.Serialize(strm, tasks);
+ strm.Close();
+ strm.Dispose();
}
}
/// <summary>
- /// Invoke the QueueCompleted event.
+ /// Checks the current queue for an existing instance of the specified destination.
/// </summary>
- /// <param name="e">
- /// The EventArgs.
+ /// <param name="destination">
+ /// The destination of the encode.
/// </param>
- private void InvokeQueueCompleted(EventArgs e)
+ /// <returns>
+ /// Whether or not the supplied destination is already in the queue.
+ /// </returns>
+ public bool CheckForDestinationPathDuplicates(string destination)
{
- this.IsProcessing = false;
+ return this.queue.Any(checkItem => checkItem.Task.Destination.Contains(destination.Replace("\\\\", "\\")));
+ }
- EventHandler handler = this.QueueCompleted;
- if (handler != null)
+ /// <summary>
+ /// Clear down all Queue Items
+ /// </summary>
+ public void Clear()
+ {
+ List<QueueTask> deleteList = this.queue.ToList();
+ foreach (QueueTask item in deleteList)
{
- handler(this, e);
+ this.queue.Remove(item);
}
+ this.InvokeQueueChanged(EventArgs.Empty);
}
- #endregion
+ /// <summary>
+ /// Clear down the Queue´s completed items
+ /// </summary>
+ public void ClearCompleted()
+ {
+ Execute.OnUIThread(
+ () =>
+ {
+ List<QueueTask> deleteList =
+ this.queue.Where(task => task.Status == QueueItemStatus.Completed).ToList();
+ foreach (QueueTask item in deleteList)
+ {
+ this.queue.Remove(item);
+ }
+ this.InvokeQueueChanged(EventArgs.Empty);
+ });
+ }
- #region Properties
+ /// <summary>
+ /// Get the first job on the queue for processing.
+ /// This also removes the job from the Queue and sets the LastProcessedJob
+ /// </summary>
+ /// <returns>
+ /// An encode Job object.
+ /// </returns>
+ public QueueTask GetNextJobForProcessing()
+ {
+ if (this.queue.Count > 0)
+ {
+ QueueTask job = this.queue.FirstOrDefault(q => q.Status == QueueItemStatus.Waiting);
+ if (job != null)
+ {
+ job.Status = QueueItemStatus.InProgress;
+ this.LastProcessedJob = job;
+ this.InvokeQueueChanged(EventArgs.Empty);
+ }
+
+ this.BackupQueue(string.Empty);
+ return job;
+ }
+
+ this.BackupQueue(string.Empty);
+ return null;
+ }
/// <summary>
- /// Gets a value indicating whether IsProcessing.
+ /// Moves an item down one position in the queue.
/// </summary>
- public bool IsProcessing { get; private set; }
+ /// <param name="index">
+ /// The zero-based location of the job in the queue.
+ /// </param>
+ public void MoveDown(int index)
+ {
+ if (index < this.queue.Count - 1)
+ {
+ QueueTask item = this.queue[index];
+
+ this.queue.RemoveAt(index);
+ this.queue.Insert((index + 1), item);
+ }
+
+ this.InvokeQueueChanged(EventArgs.Empty);
+ }
/// <summary>
- /// Gets the IEncodeService instance.
+ /// Moves an item up one position in the queue.
/// </summary>
- public IEncodeServiceWrapper EncodeService { get; private set; }
+ /// <param name="index">
+ /// The zero-based location of the job in the queue.
+ /// </param>
+ public void MoveUp(int index)
+ {
+ if (index > 0)
+ {
+ QueueTask item = this.queue[index];
+
+ this.queue.RemoveAt(index);
+ this.queue.Insert((index - 1), item);
+ }
+
+ this.InvokeQueueChanged(EventArgs.Empty);
+ }
/// <summary>
- /// Gets the IQueueManager instance.
+ /// Remove a job from the Queue.
+ /// This method is Thread Safe
/// </summary>
- public IQueueManager QueueManager { get; private set; }
+ /// <param name="job">
+ /// The job.
+ /// </param>
+ public void Remove(QueueTask job)
+ {
+ lock (QueueLock)
+ {
+ this.queue.Remove(job);
+ this.InvokeQueueChanged(EventArgs.Empty);
+ }
+ }
- #endregion
+ /// <summary>
+ /// Temp workaround until this can be fixed properly.
+ /// </summary>
+ public void ResetInstanceId()
+ {
+ this.queueFile = string.Format("hb_queue_recovery{0}.xml", GeneralUtilities.GetInstanceCount);
+ }
/// <summary>
- /// Starts encoding the first job in the queue and continues encoding until all jobs
- /// have been encoded.
+ /// Reset a Queued Item from Error or Completed to Waiting
/// </summary>
- public void Start()
+ /// <param name="job">
+ /// The job.
+ /// </param>
+ public void ResetJobStatusToWaiting(QueueTask job)
{
- if (IsProcessing)
+ if (job.Status != QueueItemStatus.Error && job.Status != QueueItemStatus.Completed)
{
- throw new Exception("Already Processing the Queue");
+ throw new GeneralApplicationException(
+ "Job Error", "Unable to reset job status as it is not in an Error or Completed state", null);
}
- IsProcessing = true;
- this.EncodeService.EncodeCompleted += this.EncodeServiceEncodeCompleted;
- this.ProcessNextJob();
+ job.Status = QueueItemStatus.Waiting;
+ }
+
+ /// <summary>
+ /// Restore a Queue from file or from the queue backup file.
+ /// </summary>
+ /// <param name="importPath">
+ /// The import path. String.Empty or null will result in the default file being loaded.
+ /// </param>
+ public void RestoreQueue(string importPath)
+ {
+ string appDataPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"HandBrake\");
+ string tempPath = !string.IsNullOrEmpty(importPath)
+ ? importPath
+ : (appDataPath + string.Format(this.queueFile, string.Empty));
+
+ if (File.Exists(tempPath))
+ {
+ bool invokeUpdate = false;
+ using (
+ var strm = new FileStream(
+ (!string.IsNullOrEmpty(importPath) ? importPath : tempPath), FileMode.Open, FileAccess.Read))
+ {
+ if (strm.Length != 0)
+ {
+ var serializer = new XmlSerializer(typeof(List<QueueTask>));
+
+ List<QueueTask> list;
+
+ try
+ {
+ list = serializer.Deserialize(strm) as List<QueueTask>;
+ }
+ catch (Exception exc)
+ {
+ throw new GeneralApplicationException(
+ "Unable to restore queue file.",
+ "The file may be corrupted or from an older incompatible version of HandBrake",
+ exc);
+ }
+
+ if (list != null)
+ {
+ foreach (QueueTask item in list)
+ {
+ if (item.Status != QueueItemStatus.Completed)
+ {
+ // Reset InProgress/Error to Waiting so it can be processed
+ if (item.Status == QueueItemStatus.InProgress)
+ {
+ item.Status = QueueItemStatus.Waiting;
+ }
+
+ this.queue.Add(item);
+ }
+ }
+ }
+
+ invokeUpdate = true;
+ }
+ }
+
+ if (invokeUpdate)
+ {
+ this.InvokeQueueChanged(EventArgs.Empty);
+ }
+ }
}
/// <summary>
@@ -182,17 +432,25 @@ namespace HandBrake.ApplicationServices.Services
}
/// <summary>
- /// Swap encode service.
- /// Temp method until Castle is hooked up.
+ /// Starts encoding the first job in the queue and continues encoding until all jobs
+ /// have been encoded.
/// </summary>
- /// <param name="service">
- /// The service.
- /// </param>
- public void SwapEncodeService(IEncodeServiceWrapper service)
+ public void Start()
{
- this.EncodeService = service;
+ if (this.IsProcessing)
+ {
+ throw new Exception("Already Processing the Queue");
+ }
+
+ this.IsProcessing = true;
+ this.EncodeService.EncodeCompleted += this.EncodeServiceEncodeCompleted;
+ this.ProcessNextJob();
}
+ #endregion
+
+ #region Methods
+
/// <summary>
/// After an encode is complete, move onto the next job.
/// </summary>
@@ -204,39 +462,139 @@ namespace HandBrake.ApplicationServices.Services
/// </param>
private void EncodeServiceEncodeCompleted(object sender, EncodeCompletedEventArgs e)
{
- this.QueueManager.LastProcessedJob.Status = QueueItemStatus.Completed;
+ this.LastProcessedJob.Status = QueueItemStatus.Completed;
// Clear the completed item of the queue if the setting is set.
- if (userSettingService.GetUserSetting<bool>(ASUserSettingConstants.ClearCompletedFromQueue))
+ if (this.userSettingService.GetUserSetting<bool>(ASUserSettingConstants.ClearCompletedFromQueue))
{
- this.QueueManager.ClearCompleted();
+ this.ClearCompleted();
}
if (!e.Successful)
{
- this.QueueManager.LastProcessedJob.Status = QueueItemStatus.Error;
+ this.LastProcessedJob.Status = QueueItemStatus.Error;
this.Pause();
}
// Handling Log Data
- this.EncodeService.ProcessLogs(this.QueueManager.LastProcessedJob.Task.Destination);
+ this.EncodeService.ProcessLogs(this.LastProcessedJob.Task.Destination);
// Post-Processing
if (e.Successful)
{
- SendToApplication(this.QueueManager.LastProcessedJob.Task.Destination);
+ this.SendToApplication(this.LastProcessedJob.Task.Destination);
}
// Move onto the next job.
if (this.IsProcessing)
{
this.ProcessNextJob();
- }
- else
+ }
+ else
{
this.EncodeService.EncodeCompleted -= this.EncodeServiceEncodeCompleted;
this.InvokeQueueCompleted(EventArgs.Empty);
- this.QueueManager.BackupQueue(string.Empty);
+ this.BackupQueue(string.Empty);
+ }
+ }
+
+ /// <summary>
+ /// Perform an action after an encode. e.g a shutdown, standby, restart etc.
+ /// </summary>
+ private void Finish()
+ {
+ // Do something whent he encode ends.
+ switch (this.userSettingService.GetUserSetting<string>(ASUserSettingConstants.WhenCompleteAction))
+ {
+ case "Shutdown":
+ Process.Start("Shutdown", "-s -t 60");
+ break;
+ case "Log off":
+ Win32.ExitWindowsEx(0, 0);
+ break;
+ case "Suspend":
+ Application.SetSuspendState(PowerState.Suspend, true, true);
+ break;
+ case "Hibernate":
+ Application.SetSuspendState(PowerState.Hibernate, true, true);
+ break;
+ case "Lock System":
+ Win32.LockWorkStation();
+ break;
+ case "Quit HandBrake":
+ Application.Exit();
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Invoke the JobProcessingStarted event
+ /// </summary>
+ /// <param name="e">
+ /// The QueueProgressEventArgs.
+ /// </param>
+ private void InvokeJobProcessingStarted(QueueProgressEventArgs e)
+ {
+ QueueProgressStatus handler = this.JobProcessingStarted;
+ if (handler != null)
+ {
+ handler(this, e);
+ }
+ }
+
+ /// <summary>
+ /// Invoke the Queue Changed Event
+ /// </summary>
+ /// <param name="e">
+ /// The e.
+ /// </param>
+ private void InvokeQueueChanged(EventArgs e)
+ {
+ try
+ {
+ this.BackupQueue(string.Empty);
+ }
+ catch (Exception)
+ {
+ // Do Nothing.
+ }
+
+ EventHandler handler = this.QueueChanged;
+ if (handler != null)
+ {
+ handler(this, e);
+ }
+ }
+
+ /// <summary>
+ /// Invoke the QueueCompleted event.
+ /// </summary>
+ /// <param name="e">
+ /// The EventArgs.
+ /// </param>
+ private void InvokeQueueCompleted(EventArgs e)
+ {
+ this.IsProcessing = false;
+
+ EventHandler handler = this.QueueCompleted;
+ if (handler != null)
+ {
+ handler(this, e);
+ }
+ }
+
+ /// <summary>
+ /// Invoke the QueuePaused event
+ /// </summary>
+ /// <param name="e">
+ /// The EventArgs.
+ /// </param>
+ private void InvokeQueuePaused(EventArgs e)
+ {
+ EventHandler handler = this.QueuePaused;
+ if (handler != null)
+ {
+ handler(this, e);
}
}
@@ -252,11 +610,11 @@ namespace HandBrake.ApplicationServices.Services
return;
}
- QueueTask job = this.QueueManager.GetNextJobForProcessing();
+ QueueTask job = this.GetNextJobForProcessing();
if (job != null)
{
this.InvokeJobProcessingStarted(new QueueProgressEventArgs(job));
- this.EncodeService.Start(job, true);
+ this.EncodeService.Start(job, true);
}
else
{
@@ -267,53 +625,32 @@ namespace HandBrake.ApplicationServices.Services
this.InvokeQueueCompleted(EventArgs.Empty);
// Run the After encode completeion work
- Finish();
+ this.Finish();
}
}
/// <summary>
/// Send a file to a 3rd party application after encoding has completed.
/// </summary>
- /// <param name="file"> The file path</param>
+ /// <param name="file">
+ /// The file path
+ /// </param>
private void SendToApplication(string file)
{
- if (userSettingService.GetUserSetting<bool>(ASUserSettingConstants.SendFile) && !string.IsNullOrEmpty(userSettingService.GetUserSetting<string>(ASUserSettingConstants.SendFileTo)))
+ if (this.userSettingService.GetUserSetting<bool>(ASUserSettingConstants.SendFile) &&
+ !string.IsNullOrEmpty(this.userSettingService.GetUserSetting<string>(ASUserSettingConstants.SendFileTo)))
{
- string args = string.Format("{0} \"{1}\"", userSettingService.GetUserSetting<string>(ASUserSettingConstants.SendFileToArgs), file);
- ProcessStartInfo vlc = new ProcessStartInfo(userSettingService.GetUserSetting<string>(ASUserSettingConstants.SendFileTo), args);
+ string args = string.Format(
+ "{0} \"{1}\"",
+ this.userSettingService.GetUserSetting<string>(ASUserSettingConstants.SendFileToArgs),
+ file);
+ var vlc =
+ new ProcessStartInfo(
+ this.userSettingService.GetUserSetting<string>(ASUserSettingConstants.SendFileTo), args);
Process.Start(vlc);
}
}
- /// <summary>
- /// Perform an action after an encode. e.g a shutdown, standby, restart etc.
- /// </summary>
- private void Finish()
- {
- // Do something whent he encode ends.
- switch (userSettingService.GetUserSetting<string>(ASUserSettingConstants.WhenCompleteAction))
- {
- case "Shutdown":
- Process.Start("Shutdown", "-s -t 60");
- break;
- case "Log off":
- Win32.ExitWindowsEx(0, 0);
- break;
- case "Suspend":
- Application.SetSuspendState(PowerState.Suspend, true, true);
- break;
- case "Hibernate":
- Application.SetSuspendState(PowerState.Hibernate, true, true);
- break;
- case "Lock System":
- Win32.LockWorkStation();
- break;
- case "Quit HandBrake":
- Application.Exit();
- break;
- default:
- break;
- }
- }
+ #endregion
}
} \ No newline at end of file