// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// The preset service manages HandBrake's presets
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrakeWPF.Services.Presets
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Xml.Serialization;
using HandBrake.ApplicationServices.Interop;
using HandBrake.ApplicationServices.Interop.Json.Presets;
using HandBrake.ApplicationServices.Model;
using HandBrake.ApplicationServices.Utilities;
using HandBrakeWPF.Factories;
using HandBrakeWPF.Model.Picture;
using HandBrakeWPF.Properties;
using HandBrakeWPF.Services.Encode.Model.Models;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.Services.Presets.Factories;
using HandBrakeWPF.Services.Presets.Interfaces;
using HandBrakeWPF.Services.Presets.Model;
using HandBrakeWPF.Utilities;
using Newtonsoft.Json;
using GeneralApplicationException = HandBrakeWPF.Exceptions.GeneralApplicationException;
///
/// The preset service manages HandBrake's presets
///
public class PresetService : IPresetService
{
#region Private Variables
private static readonly int CurrentPresetVersion = 5;
///
/// User Preset Default Catgory Name
///
public static string UserPresetCatgoryName = "User Presets";
///
/// The User Preset file
///
private readonly string userPresetFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\user_presets.json";
///
/// The Legacy Preset file
///
private readonly string legacyUserPresetFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\user_presets.xml";
///
/// The Built In Presets File
///
private readonly string builtInPresetFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\presets.json";
///
/// A Collection of presets
///
private readonly ObservableCollection presets = new ObservableCollection();
///
/// The Error Service.
///
private readonly IErrorService errorService;
#endregion
///
/// Initializes a new instance of the class.
///
///
/// The error service.
///
public PresetService(IErrorService errorService)
{
this.errorService = errorService;
}
///
/// Gets a Collection of presets.
///
public ObservableCollection Presets
{
get
{
return this.presets;
}
}
///
/// Gets or sets LastPresetAdded.
///
public Preset LastPresetAdded { get; set; }
///
/// Gets the DefaultPreset.
///
public Preset DefaultPreset
{
get
{
return this.presets.FirstOrDefault(p => p.IsDefault);
}
}
#region Public Methods
///
/// The load.
///
public void Load()
{
// If the preset file doesn't exist. Create it.
if (!File.Exists(this.builtInPresetFile))
{
this.UpdateBuiltInPresets();
}
// Load the presets from file
this.LoadPresets();
// Check they are up-to-date.
if (this.CheckIfPresetsAreOutOfDate())
{
this.UpdateBuiltInPresets();
this.LoadPresets(); // Reload again.
}
}
///
/// Add a new preset to the system.
/// Performs an Update if it already exists
///
///
/// A Preset to add
///
///
/// True if added,
/// False if name already exists
///
public bool Add(Preset preset)
{
if (this.CheckIfPresetExists(preset.Name) == false)
{
this.presets.Add(preset);
this.LastPresetAdded = preset;
// Update the presets file
this.UpdatePresetFiles();
return true;
}
this.Update(preset);
return true;
}
///
/// The import.
///
///
/// The filename.
///
public void Import(string filename)
{
// TODO needs a tidy up but will do for now.
if (!string.IsNullOrEmpty(filename))
{
PresetTransportContainer container = HandBrakePresetService.GetPresetFromFile(filename);
if (container == null || container.PresetList == null || container.PresetList.Count == 0)
{
this.errorService.ShowError(Resources.Main_PresetImportFailed, Resources.Main_PresetImportFailedSolution, string.Empty);
return;
}
HBPreset hbPreset = container.PresetList.FirstOrDefault();
Preset preset = null;
try
{
preset = JsonPresetFactory.ImportPreset(hbPreset);
preset.Category = UserPresetCatgoryName;
// IF we are using Source Max, Set the Max Width / Height values.
if (preset.PictureSettingsMode == PresetPictureSettingsMode.SourceMaximum)
{
preset.Task.MaxWidth = preset.Task.Height;
preset.Task.MaxHeight = preset.Task.Width;
}
}
catch (Exception exc)
{
this.errorService.ShowError(Resources.Main_PresetImportFailed, Resources.Main_PresetImportFailedSolution, exc);
}
if (preset == null)
{
this.errorService.ShowError(Resources.Main_PresetImportFailed, Resources.Main_PresetImportFailedSolution, string.Empty);
return;
}
// TODO Better version checking.
if (this.CheckIfPresetExists(preset.Name))
{
if (!CanUpdatePreset(preset.Name))
{
MessageBox.Show(Resources.Main_PresetErrorBuiltInName, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
MessageBoxResult result = MessageBox.Show(Resources.Main_PresetOverwriteWarning, Resources.Overwrite, MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
Update(preset);
}
}
else
{
Add(preset);
}
}
}
///
/// The export.
///
///
/// The filename.
///
///
/// The preset.
///
///
/// The configuration.
///
public void Export(string filename, Preset preset, HBConfiguration configuration)
{
PresetTransportContainer container = JsonPresetFactory.ExportPreset(preset, configuration);
HandBrakePresetService.ExportPreset(filename, container);
}
///
/// Update a preset
///
///
/// The updated preset
///
public void Update(Preset update)
{
// TODO - Change this to be a lookup
foreach (Preset preset in this.presets)
{
if (preset.Name == update.Name)
{
preset.Task = update.Task;
preset.PictureSettingsMode = update.PictureSettingsMode;
preset.Category = update.Category;
preset.Description = update.Description;
preset.AudioTrackBehaviours = update.AudioTrackBehaviours;
preset.SubtitleTrackBehaviours = update.SubtitleTrackBehaviours;
// Update the presets file
this.UpdatePresetFiles();
break;
}
}
}
///
/// Remove a preset with a given name from either the built in or user preset list.
///
///
/// The Preset to remove
///
public void Remove(Preset preset)
{
if (preset == null || preset.IsDefault)
{
return;
}
this.presets.Remove(preset);
this.UpdatePresetFiles();
}
///
/// Remove a group of presets by category
///
///
/// The Category to remove
///
public void RemoveGroup(string category)
{
List removeList = this.presets.Where(p => p.Category == category).ToList();
foreach (Preset preset in removeList)
{
if (preset.IsDefault)
{
// Skip default preset
continue;
}
this.presets.Remove(preset);
}
this.UpdatePresetFiles();
}
///
/// Set Default Preset
///
///
/// The name.
///
public void SetDefault(Preset name)
{
foreach (Preset preset in this.presets)
{
preset.IsDefault = false;
}
name.IsDefault = true;
this.UpdatePresetFiles();
}
///
/// Get a Preset
///
///
/// The name of the preset to get
///
///
/// A Preset or null object
///
public Preset GetPreset(string name)
{
return this.presets.FirstOrDefault(item => item.Name == name);
}
///
/// Clear Built-in Presets
///
public void ClearBuiltIn()
{
List remove = this.presets.Where(p => p.IsBuildIn).ToList();
foreach (Preset preset in remove)
{
this.presets.Remove(preset);
}
}
///
/// Clear all presets
///
public void ClearAll()
{
this.presets.Clear();
}
///
/// Reads the CLI's CLI output format and load's them into the preset List Preset
///
public void UpdateBuiltInPresets()
{
// Clear the current built in Presets and now parse the tempory Presets file.
this.ClearBuiltIn();
IList presetCategories = HandBrakePresetService.GetBuiltInPresets();
foreach (var item in presetCategories)
{
foreach (var hbpreset in item.ChildrenArray)
{
Preset preset = JsonPresetFactory.ImportPreset(hbpreset);
preset.Version = VersionHelper.GetVersion();
preset.IsBuildIn = true; // Older versions did not have this flag so explicitly make sure it is set.
preset.Category = item.PresetName;
if (preset.Name == "iPod")
{
preset.Task.KeepDisplayAspect = true;
}
preset.Task.AllowedPassthruOptions = new AllowedPassthru(true); // We don't want to override the built-in preset
if (preset.Name == "Normal")
{
preset.IsDefault = true;
}
this.presets.Add(preset);
}
}
// Verify we have presets.
if (this.presets.Count == 0)
{
throw new GeneralApplicationException("Failed to load built-in presets.", "Restarting HandBrake may resolve this issue", null);
}
// Store the changes to disk
this.UpdatePresetFiles();
}
///
/// Check if the built in Presets stored are not out of date.
/// Update them if they are.
///
/// true if out of date
public bool CheckIfPresetsAreOutOfDate()
{
// Update built-in Presets if the built-in Presets belong to an older version.
if (this.presets.Count != 0)
{
List preset = this.presets.Where(p => p.IsBuildIn).ToList();
if (preset.Count > 0)
{
if (preset[0].Version != VersionHelper.GetVersion())
{
this.UpdateBuiltInPresets();
return true;
}
}
}
return false;
}
///
/// Check if the preset "name" exists in either Presets or UserPresets lists.
///
///
/// Name of the preset
///
///
/// True if found
///
public bool CheckIfPresetExists(string name)
{
return name == string.Empty || this.presets.Any(item => item.Name == name);
}
///
/// Returns a value if the preset can be updated / resaved
///
///
/// The name.
///
///
/// True if it's not a built-in preset, false otherwise.
///
public bool CanUpdatePreset(string name)
{
return this.presets.Where(preset => preset.Name == name).Any(preset => preset.IsBuildIn == false);
}
#endregion
#region Private Helpers
///
/// Recover from a courrpted preset file
/// Add .old to the current filename, and delete the current file.
///
///
/// The broken presets file.
///
///
/// The .
///
private static string RecoverFromCorruptedPresetFile(string file)
{
try
{
// Recover from Error.
string disabledFile = string.Format("{0}.{1}", file, GeneralUtilities.ProcessId);
if (File.Exists(file))
{
File.Move(file, disabledFile);
if (File.Exists(file))
{
File.Delete(file);
}
}
return disabledFile;
}
catch (IOException)
{
// Give up
}
return "Sorry, the archiving failed.";
}
///
/// Load in the Built-in and User presets into the collection
///
private void LoadPresets()
{
// First clear the Presets arraylists
this.presets.Clear();
// Load in the Presets from Presets.xml
try
{
if (File.Exists(this.builtInPresetFile))
{
using (StreamReader reader = new StreamReader(this.builtInPresetFile))
{
// New Preset Format.
IList presetCategories = JsonConvert.DeserializeObject>(reader.ReadToEnd());
foreach (var item in presetCategories)
{
foreach (var hbpreset in item.ChildrenArray)
{
Preset preset = JsonPresetFactory.ImportPreset(hbpreset);
preset.Category = item.PresetName;
preset.IsBuildIn = true;
// IF we are using Source Max, Set the Max Width / Height values.
if (preset.PictureSettingsMode == PresetPictureSettingsMode.SourceMaximum)
{
preset.Task.MaxWidth = preset.Task.Height;
preset.Task.MaxHeight = preset.Task.Width;
}
this.presets.Add(preset);
}
}
}
}
}
catch (Exception)
{
RecoverFromCorruptedPresetFile(this.builtInPresetFile);
this.UpdateBuiltInPresets();
}
// Load in the users Presets from UserPresets.xml
try
{
// Handle Legacy Preset Format.
bool updatePresets = false;
if (File.Exists(this.legacyUserPresetFile))
{
using (StreamReader reader = new StreamReader(this.legacyUserPresetFile))
{
try
{
XmlSerializer Ser = new XmlSerializer(typeof(List));
var oldPresets = (List)Ser.Deserialize(reader);
foreach (Preset oldPreset in oldPresets)
{
this.presets.Add(oldPreset);
}
updatePresets = true;
}
catch (Exception exc)
{
// Do Nothing
Debug.WriteLine(exc);
}
}
// Archive the old file incase the user needs it.
File.Move(this.legacyUserPresetFile, this.legacyUserPresetFile + ".archive." + GeneralUtilities.ProcessId);
}
// New JSON Format.
if (File.Exists(this.userPresetFile))
{
// New Preset Format.
bool createBackup = false;
PresetTransportContainer presetContainer = null;
using (StreamReader reader = new StreamReader(this.userPresetFile))
{
try
{
presetContainer = JsonConvert.DeserializeObject(reader.ReadToEnd());
}
catch (Exception exc)
{
createBackup = true;
Debug.WriteLine(exc);
}
}
// If we have old presets, or the container wasn't parseable, or we have a version mismatch, backup the user preset file
// incase something goes wrong.
if (createBackup || (presetContainer.VersionMajor != Constants.PresetVersionMajor || presetContainer.VersionMinor != Constants.PresetVersionMinor || presetContainer.VersionMicro != Constants.PresetVersionMicro))
{
string fileName = RecoverFromCorruptedPresetFile(this.userPresetFile);
this.errorService.ShowMessageBox(
"HandBrake is unable to load your user presets because they are from an older version of HandBrake. Your old presets file has been renamed so that it doesn't get loaded on next launch."
+ Environment.NewLine + Environment.NewLine + "Archived File: " + fileName,
"Unable to load user presets.",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
return;
}
// Load the current presets.
if (presetContainer.PresetList != null)
{
foreach (var item in presetContainer.PresetList)
{
Preset preset = JsonPresetFactory.ImportPreset(item);
preset.Category = item.PresetName;
preset.IsBuildIn = true;
// If we are using Source Max, Set the Max Width / Height values.
if (preset.PictureSettingsMode == PresetPictureSettingsMode.SourceMaximum)
{
preset.Task.MaxWidth = preset.Task.Height;
preset.Task.MaxHeight = preset.Task.Width;
}
this.presets.Add(preset);
}
}
}
// We did a preset convertion, so save the updates.
if (updatePresets)
{
this.UpdatePresetFiles();
}
}
catch (Exception exc)
{
RecoverFromCorruptedPresetFile(this.userPresetFile);
throw new GeneralApplicationException("HandBrake has detected a problem with your presets.", "Your old presets file has been renamed so that it doesn't get loaded on next launch.", exc);
}
}
///
/// Update the preset files
///
private void UpdatePresetFiles()
{
try
{
// Setup
string directory = Path.GetDirectoryName(this.userPresetFile);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
JsonSerializerSettings settings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore };
// Built-in Presets
Dictionary presetCategories = new Dictionary();
foreach (var item in this.presets.Where(p => p.IsBuildIn).ToList())
{
HBPreset preset = JsonPresetFactory.CreateHbPreset(item, HBConfigurationFactory.Create());
if (presetCategories.ContainsKey(item.Category))
{
presetCategories[item.Category].ChildrenArray.Add(preset);
}
else
{
presetCategories[item.Category] = new PresetCategory { ChildrenArray = new List(), Folder = true, PresetName = item.Category, Type = 0 };
}
}
using (FileStream strm = new FileStream(this.builtInPresetFile, FileMode.Create, FileAccess.Write))
{
string presetsJson = JsonConvert.SerializeObject(presetCategories, Formatting.Indented, settings);
using (StreamWriter writer = new StreamWriter(strm))
{
writer.WriteLine(presetsJson);
}
}
// User Presets
using (FileStream strm = new FileStream(this.userPresetFile, FileMode.Create, FileAccess.Write))
{
List userPresets = this.presets.Where(p => p.IsBuildIn == false).ToList();
PresetTransportContainer container = JsonPresetFactory.ExportPresets(userPresets, HBConfigurationFactory.Create());
string presetsJson = JsonConvert.SerializeObject(container, Formatting.Indented, settings);
using (StreamWriter writer = new StreamWriter(strm))
{
writer.WriteLine(presetsJson);
}
}
}
catch (Exception exc)
{
throw new GeneralApplicationException("Unable to write to the presets file.", "The details section below may indicate why this error has occured.", exc);
}
}
#endregion
}
}