/* HBPresets.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBPresetsManager.h"
#import "HBPreset.h"
#import "HBUtilities.h"
#import "NSJSONSerialization+HBAdditions.h"
#include "preset.h"
NSString *HBPresetsChangedNotification = @"HBPresetsChangedNotification";
@interface HBPresetsManager ()
@property (nonatomic, readonly, copy) NSURL *fileURL;
@end
@implementation HBPresetsManager
- (instancetype)init
{
self = [super init];
if (self)
{
// Init the root of the tree, it won't never be shown in the UI
_root = [[HBPreset alloc] initWithCategoryName:@"Root" builtIn:YES];
_root.delegate = self;
}
return self;
}
- (instancetype)initWithURL:(NSURL *)url
{
self = [self init];
if (self)
{
_fileURL = [url copy];
[self loadPresetsFromURL:url];
}
return self;
}
#pragma mark - HBTreeNode delegate
- (void)nodeDidChange:(id)node
{
[[NSNotificationCenter defaultCenter] postNotificationName:HBPresetsChangedNotification object:nil];
}
- (void)treeDidRemoveNode:(id)node
{
if (node == self.defaultPreset)
{
// Select a new default preset
[self selectNewDefault];
}
}
#pragma mark - Load/Save
/**
* Loads the old presets format (0.10 and earlier) plist
*
* @param url the url of the plist
*/
- (void)loadOldPresetsFromURL:(NSURL *)url
{
NSError *error;
HBPreset *oldPresets = [[HBPreset alloc] initWithContentsOfURL:url error:&error];
for (HBPreset *preset in oldPresets.children)
{
[self.root insertObject:preset inChildrenAtIndex:self.root.countOfChildren];
}
}
- (BOOL)isNewer:(NSDictionary *)dict
{
int major, minor, micro;
hb_presets_current_version(&major, &minor, µ);
if ([dict[@"VersionMajor"] intValue] > major ||
([dict[@"VersionMajor"] intValue] == major && [dict[@"VersionMinor"] intValue] > minor) ||
([dict[@"VersionMajor"] intValue] == major && [dict[@"VersionMinor"] intValue] == minor && [dict[@"VersionMicro"] intValue] > micro))
{
return YES;
}
return NO;
}
- (BOOL)isOutOfDate:(NSDictionary *)dict
{
int major, minor, micro;
hb_presets_current_version(&major, &minor, µ);
if (major != [dict[@"VersionMajor"] intValue] ||
minor != [dict[@"VersionMinor"] intValue] ||
micro != [dict[@"VersionMicro"] intValue])
{
return YES;
}
return NO;
}
- (NSURL *)backupURLfromURL:(NSURL *)url versionMajor:(int)major minor:(int)minor micro:(int)micro
{
NSString *backupName = [NSString stringWithFormat:@"%@.%d.%d.%d.json",
url.lastPathComponent.stringByDeletingPathExtension,
major, minor, micro];
return [url.URLByDeletingLastPathComponent URLByAppendingPathComponent:backupName];
}
typedef NS_ENUM(NSUInteger, HBPresetLoadingResult) {
HBPresetLoadingResultOK,
HBPresetLoadingResultOKUpgraded,
HBPresetLoadingResultFailedNewer,
HBPresetLoadingResultFailed
};
- (NSDictionary *)dictionaryWithPresetsAtURL:(NSURL *)url backup:(BOOL)backup result:(HBPresetLoadingResult *)result;
{
NSData *presetData = [[NSData alloc] initWithContentsOfURL:url];
if (presetData)
{
const char *json = [[NSString alloc] initWithData:presetData encoding:NSUTF8StringEncoding].UTF8String;
NSDictionary *presetsDict = [NSJSONSerialization HB_JSONObjectWithUTF8String:json options:0 error:NULL];
if ([presetsDict isKindOfClass:[NSDictionary class]])
{
if ([self isNewer:presetsDict])
{
*result = HBPresetLoadingResultFailedNewer;
return nil;
}
else if ([self isOutOfDate:presetsDict])
{
// There is a new presets file format,
// make a backup of the old one for the previous versions.
if (backup)
{
NSURL *backupURL = [self backupURLfromURL:self.fileURL
versionMajor:[presetsDict[@"VersionMajor"] intValue]
minor:[presetsDict[@"VersionMinor"] intValue]
micro:[presetsDict[@"VersionMicro"] intValue]];
[[NSFileManager defaultManager] copyItemAtURL:url toURL:backupURL error:NULL];
}
// Update the presets to the current format.
char *updatedJson;
hb_presets_import_json(json, &updatedJson);
presetsDict = [NSJSONSerialization HB_JSONObjectWithUTF8String:updatedJson options:0 error:NULL];
free(updatedJson);
*result = HBPresetLoadingResultOKUpgraded;
}
else
{
// Else, clean up the presets just to be sure
// it's in the right format.
char *cleanedJson = hb_presets_clean_json(json);
presetsDict = [NSJSONSerialization HB_JSONObjectWithUTF8String:cleanedJson options:0 error:NULL];
free(cleanedJson);
*result = HBPresetLoadingResultOK;
}
return presetsDict;
}
}
*result = HBPresetLoadingResultFailed;
return nil;
}
- (void)loadPresetsFromURL:(NSURL *)url;
{
HBPresetLoadingResult result;
NSDictionary *presetsDict;
// Load the presets
presetsDict = [self dictionaryWithPresetsAtURL:url backup:YES result:&result];
if (result == HBPresetLoadingResultFailedNewer)
{
// Try a backup
int major, minor, micro;
hb_presets_current_version(&major, &minor, µ);
NSURL *backupURL = [self backupURLfromURL:url versionMajor:major minor:minor micro:micro];
HBPresetLoadingResult backupResult;
presetsDict = [self dictionaryWithPresetsAtURL:backupURL backup:NO result:&backupResult];
// Change the fileURL so we don't overwrite
// the latest version presets.
_fileURL = backupURL;
}
// Add the presets to the root
if (presetsDict)
{
for (NSDictionary *child in presetsDict[@"PresetList"])
{
[self.root insertObject:[[HBPreset alloc] initWithDictionary:child] inChildrenAtIndex:self.root.countOfChildren];
}
if (result == HBPresetLoadingResultOKUpgraded)
{
[self generateBuiltInPresets];
}
}
// If there is no json presets file try to read the old plist
if (result == HBPresetLoadingResultFailed && presetsDict == nil)
{
// Try to load to load the old presets file
// if the new one is empty
[self loadOldPresetsFromURL:[url.URLByDeletingPathExtension URLByAppendingPathExtension:@"plist"]];
[self generateBuiltInPresets];
}
// If the preset list contains no leaf,
// add back the built in presets.
__block BOOL leafFound = NO;
[self.root enumerateObjectsUsingBlock:^(id obj, NSIndexPath *idx, BOOL *stop) {
if ([obj isLeaf])
{
leafFound = YES;
*stop = YES;
}
}];
if (!leafFound)
{
[self generateBuiltInPresets];
}
[self selectNewDefault];
}
- (BOOL)savePresetsToURL:(NSURL *)url
{
return [self.root writeToURL:url atomically:YES format:HBPresetFormatJson removeRoot:YES];
}
- (BOOL)savePresets
{
return [self savePresetsToURL:self.fileURL];
}
#pragma mark - Presets Management
- (void)addPreset:(HBPreset *)preset
{
// Make sure no preset has the default flag enabled.
[preset enumerateObjectsUsingBlock:^(id obj, NSIndexPath *idx, BOOL *stop) {
[obj setIsDefault:NO];
}];
[self.root insertObject:preset inChildrenAtIndex:[self.root countOfChildren]];
[self savePresets];
}
- (void)deletePresetAtIndexPath:(NSIndexPath *)idx
{
[self.root removeObjectAtIndexPath:idx];
}
- (NSIndexPath *)indexPathOfPreset:(HBPreset *)preset
{
return [self.root indexPathOfObject:preset];
}
#pragma mark - Default preset
/**
* Private method to select a new default after the default preset is deleted
* or when the built-in presets are regenerated.
*/
- (void)selectNewDefault
{
__block HBPreset *normalPreset = nil;
__block HBPreset *firstUserPreset = nil;
__block HBPreset *firstBuiltInPreset = nil;
__block BOOL defaultAlreadySetted = NO;
// Search for a possible new default preset
// Try to use "Normal" or the first user preset.
[self.root enumerateObjectsUsingBlock:^(id obj, NSIndexPath *idx, BOOL *stop) {
if ([obj isBuiltIn] && [obj isLeaf])
{
if ([[obj name] isEqualToString:@"Fast 1080p30"])
{
normalPreset = obj;
}
if (firstBuiltInPreset == nil)
{
firstBuiltInPreset = obj;
}
}
else if ([obj isLeaf] && firstUserPreset == nil)
{
firstUserPreset = obj;
}
if ([obj isDefault])
{
self.defaultPreset = obj;
defaultAlreadySetted = YES;
}
[obj setIsDefault:NO];
}];
if (defaultAlreadySetted)
{
self.defaultPreset.isDefault = YES;
return;
}
else if (normalPreset)
{
self.defaultPreset = normalPreset;
normalPreset.isDefault = YES;
}
else if (firstUserPreset)
{
self.defaultPreset = firstUserPreset;
firstUserPreset.isDefault = YES;
}
else if (firstBuiltInPreset)
{
self.defaultPreset = firstBuiltInPreset;
firstBuiltInPreset.isDefault = YES;
}
}
- (void)setDefaultPreset:(HBPreset *)defaultPreset
{
if (defaultPreset && defaultPreset.isLeaf)
{
if (_defaultPreset)
{
_defaultPreset.isDefault = NO;
}
defaultPreset.isDefault = YES;
_defaultPreset = defaultPreset;
}
}
#pragma mark - Built In Generation
/**
* Built-in preset categories at the root of the hierarchy
*/
- (void)generateBuiltInPresets
{
// Load the built-in presets from libhb.
const char *presets = hb_presets_builtin_get_json();
NSData *presetsData = [NSData dataWithBytes:presets length:strlen(presets)];
NSError *error = nil;
NSArray *presetsArray = [NSJSONSerialization JSONObjectWithData:presetsData options:NSJSONReadingAllowFragments error:&error];
if (presetsArray.count == 0)
{
[HBUtilities writeToActivityLog:"failed to update built-in presets"];
if (error)
{
[HBUtilities writeToActivityLog:"Error raised:\n%s", error.localizedFailureReason];
}
}
else
{
[self deleteBuiltInPresets];
for (NSDictionary *child in presetsArray.reverseObjectEnumerator)
{
HBPreset *preset = [[HBPreset alloc] initWithDictionary:child];
[self.root insertObject:preset inChildrenAtIndex:0];
}
// set a new Default preset
[self selectNewDefault];
[HBUtilities writeToActivityLog: "built-in presets updated"];
}
}
- (void)deleteBuiltInPresets
{
[self willChangeValueForKey:@"root"];
NSMutableArray *nodeToRemove = [[NSMutableArray alloc] init];
for (HBPreset *node in self.root.children)
{
if (node.isBuiltIn)
{
[nodeToRemove addObject:node];
}
}
[self.root.children removeObjectsInArray:nodeToRemove];
[self didChangeValueForKey:@"root"];
}
@end