// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// The instant view model.
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrakeWPF.ViewModels
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using Caliburn.Micro;
using HandBrake.ApplicationServices;
using HandBrake.ApplicationServices.EventArgs;
using HandBrake.ApplicationServices.Model;
using HandBrake.ApplicationServices.Parsing;
using HandBrake.ApplicationServices.Services.Interfaces;
using HandBrake.ApplicationServices.Utilities;
using HandBrakeWPF.Commands;
using HandBrakeWPF.Helpers;
using HandBrakeWPF.Model;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.ViewModels.Interfaces;
using HandBrakeWPF.Views;
using Microsoft.Win32;
using Ookii.Dialogs.Wpf;
///
/// The instant view model.
///
public class InstantViewModel : ViewModelBase, IInstantViewModel
{
#region Constants and Fields
///
/// The encode service.
///
private readonly IEncodeServiceWrapper encodeService;
///
/// The error service.
///
private readonly IErrorService errorService;
///
/// The preset service.
///
private readonly IPresetService presetService;
///
/// The queue processor.
///
private readonly IQueueProcessor queueProcessor;
///
/// The scan service.
///
private readonly IScanServiceWrapper scanService;
///
/// The shell view model.
///
private readonly IShellViewModel shellViewModel;
///
/// The update service.
///
private readonly IUpdateService updateService;
///
/// The user setting service.
///
private readonly IUserSettingService userSettingService;
///
/// Windows 7 API Pack wrapper
///
private readonly Win7 windowsSeven = new Win7();
///
/// The is encoding.
///
private bool isEncoding;
///
/// The last percentage complete value.
///
private int lastEncodePercentage;
///
/// The ordered by duration.
///
private bool orderedByDuration;
///
/// The ordered by title.
///
private bool orderedByTitle;
///
/// The output directory.
///
private string outputDirectory;
///
/// The program status label.
///
private string programStatusLabel;
///
/// The scanned source.
///
private Source scannedSource;
///
/// The selected preset.
///
private Preset selectedPreset;
///
/// The show status window.
///
private bool showStatusWindow;
///
/// The source label.
///
private string sourceLabel;
///
/// The status label.
///
private string statusLabel;
#endregion
#region Constructors and Destructors
///
/// Initializes a new instance of the class.
///
///
/// The user setting service.
///
///
/// The scan service.
///
///
/// The encode service.
///
///
/// The preset service.
///
///
/// The error service.
///
///
/// The shell view model.
///
///
/// The update service.
///
///
/// The notification service.
///
///
/// The when done service.
///
public InstantViewModel(
IUserSettingService userSettingService,
IScanServiceWrapper scanService,
IEncodeServiceWrapper encodeService,
IPresetService presetService,
IErrorService errorService,
IShellViewModel shellViewModel,
IUpdateService updateService,
INotificationService notificationService,
IPrePostActionService whenDoneService)
{
this.userSettingService = userSettingService;
this.scanService = scanService;
this.encodeService = encodeService;
this.presetService = presetService;
this.errorService = errorService;
this.shellViewModel = shellViewModel;
this.updateService = updateService;
this.queueProcessor = IoC.Get();
// Setup Properties
this.TitleList = new BindingList();
this.ScannedSource = new Source();
// Setup Events
this.scanService.ScanStared += this.ScanStared;
this.scanService.ScanCompleted += this.ScanCompleted;
this.scanService.ScanStatusChanged += this.ScanStatusChanged;
this.queueProcessor.JobProcessingStarted += this.QueueProcessorJobProcessingStarted;
this.queueProcessor.QueueCompleted += this.QueueCompleted;
this.queueProcessor.QueueChanged += this.QueueChanged;
this.queueProcessor.EncodeService.EncodeStatusChanged += this.EncodeStatusChanged;
this.Presets = this.presetService.Presets;
this.CancelScanCommand = new CancelScanCommand(this.scanService);
}
#endregion
#region Properties
///
/// Gets or sets the cancel scan command.
///
public CancelScanCommand CancelScanCommand { get; set; }
///
/// Gets or sets a value indicating whether IsEncoding.
///
public bool IsEncoding
{
get
{
return this.isEncoding;
}
set
{
this.isEncoding = value;
this.NotifyOfPropertyChange(() => this.IsEncoding);
}
}
///
/// Gets or sets a value indicating whether ordered by duration.
///
public bool OrderedByDuration
{
get
{
return this.orderedByDuration;
}
set
{
this.orderedByDuration = value;
this.NotifyOfPropertyChange(() => this.OrderedByDuration);
}
}
///
/// Gets or sets a value indicating whether ordered by title.
///
public bool OrderedByTitle
{
get
{
return this.orderedByTitle;
}
set
{
this.orderedByTitle = value;
this.NotifyOfPropertyChange(() => this.OrderedByTitle);
}
}
///
/// Gets or sets the output directory.
///
public string OutputDirectory
{
get
{
return this.outputDirectory;
}
set
{
this.outputDirectory = value;
this.NotifyOfPropertyChange(() => this.OutputDirectory);
}
}
///
/// Gets or sets Presets.
///
public IEnumerable Presets { get; set; }
///
/// Gets or sets the Program Status Toolbar Label
/// This indicates the status of HandBrake
///
public string ProgramStatusLabel
{
get
{
return string.IsNullOrEmpty(this.programStatusLabel) ? "Ready" : this.programStatusLabel;
}
set
{
if (!Equals(this.programStatusLabel, value))
{
this.programStatusLabel = value;
this.NotifyOfPropertyChange(() => this.ProgramStatusLabel);
}
}
}
///
/// Gets or sets a value indicating progress percentage.
///
public int ProgressPercentage { get; set; }
///
/// Gets or sets the Last Scanned Source
/// This object contains information about the scanned source.
///
public Source ScannedSource
{
get
{
return this.scannedSource;
}
set
{
this.scannedSource = value;
this.NotifyOfPropertyChange("ScannedSource");
}
}
///
/// Gets or sets SelectedPreset.
///
public Preset SelectedPreset
{
get
{
return this.selectedPreset;
}
set
{
this.selectedPreset = value;
this.NotifyOfPropertyChange(() => this.SelectedPreset);
}
}
///
/// Gets or sets a value indicating whether ShowStatusWindow.
///
public bool ShowStatusWindow
{
get
{
return this.showStatusWindow;
}
set
{
this.showStatusWindow = value;
this.NotifyOfPropertyChange(() => this.ShowStatusWindow);
}
}
///
/// Gets or sets the Source Label
/// This indicates the status of scans.
///
public string SourceLabel
{
get
{
return string.IsNullOrEmpty(this.sourceLabel) ? "Select 'Source' to continue" : this.sourceLabel;
}
set
{
if (!Equals(this.sourceLabel, value))
{
this.sourceLabel = value;
this.NotifyOfPropertyChange("SourceLabel");
}
}
}
///
/// Gets SourceName.
///
public string SourceName
{
get
{
// Sanity Check
if (this.ScannedSource == null || this.ScannedSource.ScanPath == null)
{
return string.Empty;
}
// The title that is selected has a source name. This means it's part of a batch scan.
// if (selectedTitle != null && !string.IsNullOrEmpty(selectedTitle.SourceName))
// {
// return Path.GetFileNameWithoutExtension(selectedTitle.SourceName);
// }
// Check if we have a Folder, if so, check if it's a DVD / Bluray drive and get the label.
if (this.ScannedSource.ScanPath.EndsWith("\\"))
{
foreach (DriveInformation item in GeneralUtilities.GetDrives())
{
if (item.RootDirectory.Contains(this.ScannedSource.ScanPath))
{
return item.VolumeLabel;
}
}
}
if (Path.GetFileNameWithoutExtension(this.ScannedSource.ScanPath) != "VIDEO_TS")
{
return Path.GetFileNameWithoutExtension(this.ScannedSource.ScanPath);
}
return Path.GetFileNameWithoutExtension(Path.GetDirectoryName(this.ScannedSource.ScanPath));
}
}
///
/// Gets or sets the Program Status Toolbar Label
/// This indicates the status of HandBrake
///
public string StatusLabel
{
get
{
return string.IsNullOrEmpty(this.statusLabel) ? "Ready" : this.statusLabel;
}
set
{
if (!Equals(this.statusLabel, value))
{
this.statusLabel = value;
this.NotifyOfPropertyChange(() => this.StatusLabel);
}
}
}
///
/// Gets or sets the selected titles.
///
public BindingList TitleList { get; set; }
#endregion
#region Public Methods
///
/// The Destination Path
///
public void BrowseDestination()
{
var saveFileDialog = new SaveFileDialog
{
Filter = "mp4|*.mp4;*.m4v|mkv|*.mkv",
CheckPathExists = true,
AddExtension = true,
DefaultExt = ".mp4",
OverwritePrompt = true,
};
saveFileDialog.ShowDialog();
this.OutputDirectory = Path.GetDirectoryName(saveFileDialog.FileName);
}
///
/// Cancel a Scan
///
public void CancelScan()
{
this.scanService.Stop();
}
///
/// File Scan
///
public void FileScan()
{
var dialog = new VistaOpenFileDialog { Filter = "All files (*.*)|*.*" };
dialog.ShowDialog();
this.StartScan(dialog.FileName, 0);
}
///
/// Support dropping a file onto the main window to scan.
///
///
/// The DragEventArgs.
///
public void FilesDroppedOnWindow(DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var fileNames = e.Data.GetData(DataFormats.FileDrop, true) as string[];
if (fileNames != null && fileNames.Any() &&
(File.Exists(fileNames[0]) || Directory.Exists(fileNames[0])))
{
this.StartScan(fileNames[0], 0);
}
}
e.Handled = true;
}
///
/// Folder Scan
///
public void FolderScan()
{
var dialog = new VistaFolderBrowserDialog
{
Description = "Please select a folder.",
UseDescriptionForTitle = true
};
dialog.ShowDialog();
this.StartScan(dialog.SelectedPath, 0);
}
///
/// Launch the Help pages.
///
public void LaunchHelp()
{
Process.Start("https://trac.handbrake.fr/wiki/HandBrakeGuide");
}
///
/// The on load.
///
public override void OnLoad()
{
// Check the CLI Executable.
CliCheckHelper.CheckCLIVersion();
// Perform an update check if required
// this.updateService.PerformStartupUpdateCheck(this.HandleUpdateCheckResults);
// Setup the presets.
this.presetService.Load();
if (this.presetService.CheckIfPresetsAreOutOfDate())
{
if (!this.userSettingService.GetUserSetting(UserSettingConstants.PresetNotification))
{
this.errorService.ShowMessageBox(
"HandBrake has determined your built-in presets are out of date... These presets will now be updated." +
Environment.NewLine +
"Your custom presets have not been updated so you may have to re-create these by deleting and re-adding them.",
"Preset Update",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
}
this.SelectedPreset = this.presetService.DefaultPreset;
// Log Cleaning
if (this.userSettingService.GetUserSetting(UserSettingConstants.ClearOldLogs))
{
var clearLog = new Thread(() => GeneralUtilities.ClearLogFiles(30));
clearLog.Start();
}
base.OnLoad();
}
///
/// Open the About Window
///
public void OpenAboutApplication()
{
var command = new OpenOptionsScreenCommand();
command.Execute(OptionsTab.About);
}
///
/// Open the Log Window
///
public void OpenLogWindow()
{
Window window =
Application.Current.Windows.Cast().FirstOrDefault(x => x.GetType() == typeof(LogView));
if (window != null)
{
var logvm = (ILogViewModel)window.DataContext;
logvm.SelectedTab = this.IsEncoding ? 0 : 1;
window.Activate();
}
else
{
var logvm = IoC.Get();
logvm.SelectedTab = this.IsEncoding ? 0 : 1;
this.WindowManager.ShowWindow(logvm);
}
}
///
/// Open the Options Window
///
public void OpenOptionsWindow()
{
this.shellViewModel.DisplayWindow(ShellWindow.OptionsWindow);
}
///
/// The order by duration.
///
public void OrderByDuration()
{
this.TitleList =
new BindingList(this.TitleList.OrderByDescending(o => o.Title.Duration).ToList());
this.NotifyOfPropertyChange(() => this.TitleList);
this.OrderedByTitle = false;
this.OrderedByDuration = true;
}
///
/// The order by title.
///
public void OrderByTitle()
{
this.TitleList = new BindingList(this.TitleList.OrderBy(o => o.Title.TitleNumber).ToList());
this.NotifyOfPropertyChange(() => this.TitleList);
this.OrderedByTitle = true;
this.OrderedByDuration = false;
}
///
/// Pause an Encode
///
public void PauseEncode()
{
this.queueProcessor.Pause();
}
///
/// The select all.
///
public void SelectAll()
{
foreach (SelectionTitle item in this.TitleList)
{
item.IsSelected = true;
}
}
///
/// The setup.
///
///
/// The scanned source.
///
public void Setup(Source scannedSource)
{
this.TitleList.Clear();
if (scannedSource != null)
{
IEnumerable titles = this.orderedByTitle
? scannedSource.Titles
: scannedSource.Titles.OrderByDescending(o => o.Duration).ToList();
foreach (Title item in titles)
{
var title = new SelectionTitle(item, item.SourceName) { IsSelected = true };
this.TitleList.Add(title);
}
}
}
///
/// Shutdown this View
///
public void Shutdown()
{
// Shutdown Service
this.encodeService.Shutdown();
// Unsubscribe from Events.
this.scanService.ScanStared -= this.ScanStared;
this.scanService.ScanCompleted -= this.ScanCompleted;
this.scanService.ScanStatusChanged -= this.ScanStatusChanged;
this.queueProcessor.QueueCompleted -= this.QueueCompleted;
this.queueProcessor.QueueChanged -= this.QueueChanged;
this.queueProcessor.JobProcessingStarted -= this.QueueProcessorJobProcessingStarted;
this.queueProcessor.EncodeService.EncodeStatusChanged -= this.EncodeStatusChanged;
}
///
/// Start an Encode
///
public void StartEncode()
{
// if (this.queueProcessor.IsProcessing)
// {
// this.errorService.ShowMessageBox("HandBrake is already encoding.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
// return;
// }
//// Check if we already have jobs, and if we do, just start the queue.
// if (this.queueProcessor.Count != 0)
// {
// this.queueProcessor.Start();
// return;
// }
//// Otherwise, perform Santiy Checking then add to the queue and start if everything is ok.
// if (this.SelectedTitle == null)
// {
// this.errorService.ShowMessageBox("You must first scan a source.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
// return;
// }
// if (string.IsNullOrEmpty(this.Destination))
// {
// this.errorService.ShowMessageBox("The Destination field was empty.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
// return;
// }
// if (File.Exists(this.Destination))
// {
// MessageBoxResult result = this.errorService.ShowMessageBox("The current file already exists, do you wish to overwrite it?", "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
// if (result == MessageBoxResult.No)
// {
// return;
// }
// }
//// Create the Queue Task and Start Processing
// QueueTask task = new QueueTask
// {
// Task = new EncodeTask(this.CurrentTask),
// CustomQuery = false
// };
// this.queueProcessor.Add(task);
// this.queueProcessor.Start();
// this.IsEncoding = true;
}
///
/// Start a Scan
///
///
/// The filename.
///
///
/// The title.
///
public void StartScan(string filename, int title)
{
if (!string.IsNullOrEmpty(filename))
{
this.scanService.Scan(
filename,
title,
this.UserSettingService.GetUserSetting(ASUserSettingConstants.PreviewScanCount),
null);
}
}
///
/// Stop an Encode.
///
public void StopEncode()
{
this.queueProcessor.Pause();
this.encodeService.Stop();
}
///
/// The select all.
///
public void UnSelectAll()
{
foreach (SelectionTitle item in this.TitleList)
{
item.IsSelected = false;
}
}
#endregion
#region Methods
///
/// The Encode Status has changed Handler
///
///
/// The Sender
///
///
/// The Encode Progress Event Args
///
private void EncodeStatusChanged(object sender, EncodeProgressEventArgs e)
{
int percent;
int.TryParse(Math.Round(e.PercentComplete).ToString(CultureInfo.InvariantCulture), out percent);
Execute.OnUIThread(
() =>
{
if (this.queueProcessor.EncodeService.IsEncoding)
{
string josPending = string.Empty;
if (!AppArguments.IsInstantHandBrake)
{
josPending = ", Pending Jobs {5}";
}
this.ProgramStatusLabel =
string.Format(
"{0:00.00}%, FPS: {1:000.0}, Avg FPS: {2:000.0}, Time Remaining: {3}, Elapsed: {4:hh\\:mm\\:ss}" +
josPending,
e.PercentComplete,
e.CurrentFrameRate,
e.AverageFrameRate,
e.EstimatedTimeLeft,
e.ElapsedTime,
this.queueProcessor.Count);
if (this.lastEncodePercentage != percent && this.windowsSeven.IsWindowsSeven)
{
this.windowsSeven.SetTaskBarProgress(percent);
}
this.lastEncodePercentage = percent;
this.ProgressPercentage = percent;
this.NotifyOfPropertyChange(() => this.ProgressPercentage);
}
else
{
this.ProgramStatusLabel = "Queue Finished";
this.IsEncoding = false;
if (this.windowsSeven.IsWindowsSeven)
{
this.windowsSeven.SetTaskBarProgressToNoProgress();
}
}
});
}
///
/// The queue changed.
///
///
/// The sender.
///
///
/// The EventArgs.
///
private void QueueChanged(object sender, EventArgs e)
{
Execute.OnUIThread(
() => { this.ProgramStatusLabel = string.Format("{0} Encodes Pending", this.queueProcessor.Count); });
}
///
/// The Queue has completed handler
///
///
/// The Sender
///
///
/// The EventArgs
///
private void QueueCompleted(object sender, EventArgs e)
{
this.IsEncoding = false;
Execute.OnUIThread(
() =>
{
this.ProgramStatusLabel = "Queue Finished";
this.IsEncoding = false;
if (this.windowsSeven.IsWindowsSeven)
{
this.windowsSeven.SetTaskBarProgressToNoProgress();
}
});
}
///
/// Handle the Queue Starting Event
///
///
/// The sender.
///
///
/// The e.
///
private void QueueProcessorJobProcessingStarted(object sender, QueueProgressEventArgs e)
{
Execute.OnUIThread(
() =>
{
this.ProgramStatusLabel = "Preparing to encode ...";
this.IsEncoding = true;
});
}
///
/// Handle the Scan Completed Event
///
///
/// The Sender
///
///
/// The EventArgs
///
private void ScanCompleted(object sender, ScanCompletedEventArgs e)
{
this.scanService.SouceData.CopyTo(this.ScannedSource);
this.NotifyOfPropertyChange(() => this.ScannedSource);
Execute.OnUIThread(
() =>
{
if (this.scannedSource != null)
{
this.Setup(this.scannedSource);
}
if (e.Successful)
{
this.NotifyOfPropertyChange(() => this.ScannedSource);
this.NotifyOfPropertyChange(() => this.ScannedSource.Titles);
}
this.ShowStatusWindow = false;
if (e.Successful)
{
this.SourceLabel = this.SourceName;
this.StatusLabel = "Scan Completed";
}
else if (e.Cancelled)
{
this.SourceLabel = "Scan Cancelled.";
this.StatusLabel = "Scan Cancelled.";
}
else if (e.Exception == null && e.ErrorInformation != null)
{
this.SourceLabel = "Scan failed: " + e.ErrorInformation;
this.StatusLabel = "Scan failed: " + e.ErrorInformation;
}
else
{
this.SourceLabel = "Scan Failed... See Activity Log for details.";
this.StatusLabel = "Scan Failed... See Activity Log for details.";
}
});
}
///
/// Handle the Scan Started Event
///
///
/// The Sender
///
///
/// The EventArgs
///
private void ScanStared(object sender, EventArgs e)
{
Execute.OnUIThread(
() =>
{
this.StatusLabel = "Scanning source, please wait...";
this.ShowStatusWindow = true;
});
}
///
/// Handle the Scan Status Changed Event.
///
///
/// The Sender
///
///
/// The EventArgs
///
private void ScanStatusChanged(object sender, ScanProgressEventArgs e)
{
this.SourceLabel = string.Format("Scanning Title {0} of {1} ({2}%)", e.CurrentTitle, e.Titles, e.Percentage);
this.StatusLabel = string.Format("Scanning Title {0} of {1} ({2}%)", e.CurrentTitle, e.Titles, e.Percentage);
}
#endregion
}
}