// -------------------------------------------------------------------------------------------------------------------- // // 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 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 public const int ForcePresetReset = 3; public static string UserPresetCatgoryName = "User Presets"; private readonly string presetFile = Path.Combine(DirectoryUtilities.GetUserStoragePath(VersionHelper.IsNightly()), "presets.json"); private readonly ObservableCollection presets = new ObservableCollection(); private readonly IErrorService errorService; private readonly IUserSettingService userSettingService; #endregion /// /// Initializes a new instance of the class. /// /// /// The error service. /// /// /// The User setting service. /// public PresetService(IErrorService errorService, IUserSettingService userSettingService) { this.errorService = errorService; this.userSettingService = userSettingService; } /// /// Gets a Collection of presets. /// public ObservableCollection Presets { get { return this.presets; } } /// /// 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.presetFile)) { this.UpdateBuiltInPresets(); } // Load the presets from file this.LoadPresets(); } /// /// 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)) { this.presets.Add(preset); // Update the presets file this.SavePresetFiles(); return true; } this.Update(preset); return true; } /// /// The import. /// /// /// The filename. /// public void Import(string filename) { if (!string.IsNullOrEmpty(filename)) { PresetTransportContainer container = null; try { container = HandBrakePresetService.GetPresetFromFile(filename); } catch (Exception exc) { this.errorService.ShowError(Resources.Main_PresetImportFailed, Resources.Main_PresetImportFailedSolution, exc); return; } if (container?.PresetList == null || container.PresetList.Count == 0) { this.errorService.ShowError(Resources.Main_PresetImportFailed, Resources.Main_PresetImportFailedSolution, Resources.NoAdditionalInformation); return; } // HBPreset Handling if (container.PresetList != null) { foreach (var objectPreset in container.PresetList) { HBPreset hbPreset = JsonConvert.DeserializeObject(objectPreset.ToString()); 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; } if (this.CheckIfPresetExists(preset.Name)) { if (!this.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) { this.Update(preset); } } else { this.Add(preset); } } } // Category Handling. // TODO maybe for a future release. } } /// /// The export. /// /// /// The filename. /// /// /// The preset. /// /// /// The configuration. /// public void Export(string filename, Preset preset, HBConfiguration configuration) { // TODO Add support for multiple export 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.SavePresetFiles(); break; } } } /// /// Replace an existing preset with a modified one. /// /// /// The existing. /// /// /// The replacement. /// public void Replace(Preset existing, Preset replacement) { this.Remove(existing); this.Add(replacement); } /// /// 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.SavePresetFiles(); } /// /// 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.SavePresetFiles(); } /// /// Set Default Preset /// /// /// The name. /// public void SetDefault(Preset name) { foreach (Preset preset in this.presets) { preset.IsDefault = false; } name.IsDefault = true; this.SavePresetFiles(); } /// /// 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 category in presetCategories) { foreach (var hbpreset in category.ChildrenArray) { Preset preset = JsonPresetFactory.ImportPreset(hbpreset); preset.IsBuildIn = true; preset.Category = category.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 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.SavePresetFiles(); } /// /// 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 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."; } /// /// Archive the presets file without deleting it. /// /// The filename to archive /// The archived filename private string ArchivePresetFile(string file) { try { // Recover from Error. string archiveFile = string.Format("{0}.{1}", file, GeneralUtilities.ProcessId); if (File.Exists(file)) { File.Copy(file, archiveFile); } return archiveFile; } 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 the presets file. try { // If we don't have a presets file. Create one for first load. if (!File.Exists(this.presetFile)) { // If this is a nightly, and we don't have a presets file, try port the main version if it exists. string releasePresetFile = Path.Combine(DirectoryUtilities.GetUserStoragePath(false), "presets.json"); if (VersionHelper.IsNightly() && File.Exists(releasePresetFile)) { File.Copy(releasePresetFile, DirectoryUtilities.GetUserStoragePath(true)); } else { this.UpdateBuiltInPresets(); return; // Update built-in presets stores the presets locally, so just return. } } // Otherwise, we already have a file, so lets try load it. PresetTransportContainer container = null; using (StreamReader reader = new StreamReader(this.presetFile)) { try { container = JsonConvert.DeserializeObject(reader.ReadToEnd()); } catch (Exception exc) { Debug.WriteLine("Failed to parse presets file: " + exc); } } // Sanity Check. Did the container deserialise. if (container == null || container.PresetList == null) { string filename = this.RecoverFromCorruptedPresetFile(this.presetFile); this.errorService.ShowMessageBox( Resources.PresetService_UnableToLoadPresets + filename, Resources.PresetService_UnableToLoad, MessageBoxButton.OK, MessageBoxImage.Exclamation); this.UpdateBuiltInPresets(); return; // Update built-in presets stores the presets locally, so just return. } // Version Check // 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 and reset built-in presets, then re-save. if (container.VersionMajor != Constants.PresetVersionMajor || container.VersionMinor != Constants.PresetVersionMinor || container.VersionMicro != Constants.PresetVersionMicro) { string fileName = this.ArchivePresetFile(this.presetFile); this.errorService.ShowMessageBox( Resources.PresetService_PresetsOutOfDate + Environment.NewLine + Environment.NewLine + Resources.PresetService_ArchiveFile + fileName, Resources.PresetService_UnableToLoad, MessageBoxButton.OK, MessageBoxImage.Exclamation); this.UpdateBuiltInPresets(); // Update built-in presets stores the presets locally, so just return. return; } // Force Upgrade of presets if (this.userSettingService.GetUserSetting(UserSettingConstants.ForcePresetReset) < ForcePresetReset) { this.userSettingService.SetUserSetting(UserSettingConstants.ForcePresetReset, ForcePresetReset); string fileName = this.ArchivePresetFile(this.presetFile); this.errorService.ShowMessageBox( Resources.Presets_PresetForceReset + Environment.NewLine + Environment.NewLine + Resources.PresetService_ArchiveFile + fileName, Resources.PresetService_UnableToLoad, MessageBoxButton.OK, MessageBoxImage.Exclamation); this.UpdateBuiltInPresets(); // Update built-in presets stores the presets locally, so just return. return; } // The presets file loaded was OK, so process it. foreach (var item in container.PresetList) { object deserialisedItem = JsonConvert.DeserializeObject(item.ToString()); // Handle Categorised Presets. PresetCategory category = deserialisedItem as PresetCategory; if (category != null && category.Folder) { foreach (HBPreset hbpreset in category.ChildrenArray) { Preset preset = JsonPresetFactory.ImportPreset(hbpreset); preset.Category = category.PresetName; preset.IsBuildIn = hbpreset.Type == 0; // 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); } } // Uncategorised Presets deserialisedItem = JsonConvert.DeserializeObject(item.ToString()); HBPreset hbPreset = deserialisedItem as HBPreset; if (hbPreset != null && !hbPreset.Folder) { Preset preset = JsonPresetFactory.ImportPreset(hbPreset); preset.Category = UserPresetCatgoryName; preset.IsBuildIn = hbPreset.Type == 1; // 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 ex) { Debug.WriteLine(ex); this.RecoverFromCorruptedPresetFile(this.presetFile); this.UpdateBuiltInPresets(); } } /// /// Update the preset files /// private void SavePresetFiles() { try { // Verify Directories. string directory = Path.GetDirectoryName(this.presetFile); if (directory != null && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // Orgamise the Presets list into Json Equivilent objects. Dictionary presetCategories = new Dictionary(); List uncategorisedPresets = new List(); foreach (Preset item in this.presets) { if (string.IsNullOrEmpty(item.Category)) { uncategorisedPresets.Add(JsonPresetFactory.CreateHbPreset(item, HBConfigurationFactory.Create())); } else { 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 { preset }, Folder = true, PresetName = item.Category, Type = item.IsBuildIn ? 0 : 1 }; } } } // Wrap the categories in a container. JsonSerializerSettings settings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore }; PresetTransportContainer container = new PresetTransportContainer( Constants.PresetVersionMajor, Constants.PresetVersionMinor, Constants.PresetVersionMicro) { PresetList = new List() }; container.PresetList.AddRange(presetCategories.Values); container.PresetList.AddRange(uncategorisedPresets); // Write the preset container out to file. using (FileStream strm = new FileStream(this.presetFile, FileMode.Create, FileAccess.Write)) { string presetsJson = JsonConvert.SerializeObject(container, Formatting.Indented, settings); using (StreamWriter writer = new StreamWriter(strm)) { writer.WriteLine(presetsJson); } } } catch (Exception exc) { Debug.WriteLine(exc); throw new GeneralApplicationException("Unable to write to the presets file.", "The details section below may indicate why this error has occured.", exc); } } #endregion } }