// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // The Queue Manager. // Thread Safe. // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrake.ApplicationServices.Services { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Windows.Forms; using System.Xml.Serialization; using Caliburn.Micro; using HandBrake.ApplicationServices.Exceptions; using HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Services.Interfaces; using HandBrake.ApplicationServices.Utilities; using EventArgs = System.EventArgs; /// /// The Queue Manager. /// Thread Safe. /// public class QueueManager : IQueueManager { /* * TODO * - Rewrite the batch script generator. * - QueueTask, switch everything to use the Task property, which is a model of all settings. */ #region Private Variables /// /// A Lock object to maintain thread safety /// static readonly object QueueLock = new object(); /// /// The Queue of Job objects /// private readonly ObservableCollection queue = new ObservableCollection(); /// /// HandBrakes Queue file with a place holder for an extra string. /// private readonly string queueFile; /// /// The ID of the job last added /// private int lastJobId; #endregion /// /// Initializes a new instance of the class. /// /// /// The instance Id. /// public QueueManager(int instanceId) { // If this is the first instance, just use the main queue file, otherwise add the instance id to the filename. this.queueFile = instanceId == 0 ? "hb_queue_recovery.xml" : string.Format("hb_queue_recovery{0}.xml", instanceId); } #region Events /// /// Initializes a new instance of the class. /// public QueueManager() { this.queueFile = "hb_queue_recovery.xml"; // TODO need to support multi-instance here. } /// /// Fires when a job is Added, Removed or Re-Ordered. /// Should be used for triggering an update of the Queue Window. /// public event EventHandler QueueChanged; /// /// Invoke the Queue Changed Event /// /// /// The e. /// private void InvokeQueueChanged(EventArgs e) { try { this.BackupQueue(string.Empty); } catch (Exception) { // Do Nothing. } EventHandler handler = this.QueueChanged; if (handler != null) { handler(this, e); } } #endregion #region Public Properties /// /// Gets or sets Last Processed Job. /// This is set when the job is poped of the queue by GetNextJobForProcessing(); /// public QueueTask LastProcessedJob { get; set; } /// /// Gets the number of jobs in the queue; /// public int Count { get { return this.queue.Where(item => item.Status == QueueItemStatus.Waiting).Count(); } } /// /// Gets The current queue. /// public ObservableCollection Queue { get { return this.queue; } } #endregion #region Public Methods /// /// Add a job to the Queue. /// This method is Thread Safe. /// /// /// The encode Job object. /// public void Add(QueueTask job) { lock (QueueLock) { queue.Add(job); InvokeQueueChanged(EventArgs.Empty); } } /// /// Remove a job from the Queue. /// This method is Thread Safe /// /// /// The job. /// public void Remove(QueueTask job) { lock (QueueLock) { queue.Remove(job); InvokeQueueChanged(EventArgs.Empty); } } /// /// Reset a Queued Item from Error or Completed to Waiting /// /// /// The job. /// public void ResetJobStatusToWaiting(QueueTask job) { if (job.Status != QueueItemStatus.Error && job.Status != QueueItemStatus.Completed) { throw new GeneralApplicationException("Job Error", "Unable to reset job status as it is not in an Error or Completed state", null); } job.Status = QueueItemStatus.Waiting; } /// /// Clear down the Queue´s completed items /// public void ClearCompleted() { Execute.OnUIThread( () => { List deleteList = this.queue.Where(task => task.Status == QueueItemStatus.Completed).ToList(); foreach (QueueTask item in deleteList) { this.queue.Remove(item); } this.InvokeQueueChanged(EventArgs.Empty); }); } /// /// Clear down all Queue Items /// public void Clear() { List deleteList = this.queue.ToList(); foreach (QueueTask item in deleteList) { this.queue.Remove(item); } this.InvokeQueueChanged(EventArgs.Empty); } /// /// Get the first job on the queue for processing. /// This also removes the job from the Queue and sets the LastProcessedJob /// /// /// An encode Job object. /// 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; InvokeQueueChanged(EventArgs.Empty); } this.BackupQueue(string.Empty); return job; } this.BackupQueue(string.Empty); return null; } /// /// Moves an item up one position in the queue. /// /// The zero-based location of the job in the queue. public void MoveUp(int index) { if (index > 0) { QueueTask item = queue[index]; queue.RemoveAt(index); queue.Insert((index - 1), item); } this.InvokeQueueChanged(EventArgs.Empty); } /// /// Moves an item down one position in the queue. /// /// The zero-based location of the job in the queue. 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); } /// /// Backup any changes to the queue file /// /// /// If this is not null or empty, this will be used instead of the standard backup location. /// public void BackupQueue(string exportPath) { string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"HandBrake\"); string tempPath = !string.IsNullOrEmpty(exportPath) ? exportPath : appDataPath + string.Format(this.queueFile, string.Empty); using (FileStream strm = new FileStream(tempPath, FileMode.Create, FileAccess.Write)) { List tasks = queue.Where(item => item.Status != QueueItemStatus.Completed).ToList(); XmlSerializer serializer = new XmlSerializer(typeof(List)); serializer.Serialize(strm, tasks); strm.Close(); strm.Dispose(); } } /// /// Restore a Queue from file or from the queue backup file. /// /// /// The import path. String.Empty or null will result in the default file being loaded. /// 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 (FileStream strm = new FileStream((!string.IsNullOrEmpty(importPath) ? importPath : tempPath), FileMode.Open, FileAccess.Read)) { if (strm.Length != 0) { XmlSerializer serializer = new XmlSerializer(typeof(List)); List list; try { list = serializer.Deserialize(strm) as List; } 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); } } } /// /// Checks the current queue for an existing instance of the specified destination. /// /// The destination of the encode. /// Whether or not the supplied destination is already in the queue. public bool CheckForDestinationPathDuplicates(string destination) { return this.queue.Any(checkItem => checkItem.Task.Destination.Contains(destination.Replace("\\\\", "\\"))); } /// /// Writes the current state of the queue in the form of a batch (.bat) file. /// /// /// The location of the file to write the batch file to. /// /// /// The write batch script to file. /// public bool WriteBatchScriptToFile(string file) { string queries = string.Empty; foreach (QueueTask queueItem in this.queue) { string qItem = QueryGeneratorUtility.GenerateQuery(new EncodeTask(queueItem.Task)); string fullQuery = '"' + Application.StartupPath + "\\HandBrakeCLI.exe" + '"' + qItem; if (queries == string.Empty) queries = queries + fullQuery; else queries = queries + " && " + fullQuery; } string strCmdLine = queries; if (file != string.Empty) { try { // Create a StreamWriter and open the file, Write the batch file query to the file and // Close the stream using (StreamWriter line = new StreamWriter(file)) { line.WriteLine(strCmdLine); } return true; } catch (Exception exc) { throw new Exception("Unable to write to the file. Please make sure that the location has the correct permissions for file writing.", exc); } } return false; } #endregion } }