From 3636e78350f26f11746b8d8405460dd9a85a2222 Mon Sep 17 00:00:00 2001 From: "thomas.willson@me.com" Date: Sat, 5 Jul 2014 20:12:35 +0000 Subject: [PATCH] Add info panel for search results. Also add double-click on search to add to queue. Fix podcast quick caching. Fix indents. Other small improvements. --- AppController.h | 107 +- AppController.m | 2842 +++++++++-------- English.lproj/MainMenu.xib | 336 +- Get_iPlayer GUI.xcodeproj/project.pbxproj | 17 - .../UserInterfaceState.xcuserstate | Bin 50529 -> 56701 bytes Programme.h | 24 +- Programme.m | 267 +- 7 files changed, 2094 insertions(+), 1499 deletions(-) diff --git a/AppController.h b/AppController.h index 6b43128b..ca58fa02 100644 --- a/AppController.h +++ b/AppController.h @@ -14,11 +14,6 @@ #import #import "NilToStringTransformer.h" -bool runDownloads=NO; -bool runUpdate=NO; -NSDictionary *tvFormats; -NSDictionary *radioFormats; - @interface AppController : NSObject { //General NSString *getiPlayerPath; @@ -28,8 +23,8 @@ NSDictionary *radioFormats; NSString *currentTypeArgument; IBOutlet NSWindow *mainWindow; IBOutlet NSApplication *application; - IBOutlet NSWindow *historyWindow; - IOPMAssertionID powerAssertionID; + IBOutlet NSWindow *historyWindow; + IOPMAssertionID powerAssertionID; //Log Components IBOutlet NSTextView *log; @@ -41,18 +36,19 @@ NSDictionary *radioFormats; NSTask *getiPlayerUpdateTask; NSPipe *getiPlayerUpdatePipe; NSArray *getiPlayerUpdateArgs; - NSMutableArray *typesToCache; + NSMutableArray *typesToCache; BOOL didUpdate; BOOL runSinceChange; - BOOL quickUpdateFailed; - NSUInteger nextToCache; - NSDictionary *updateURLDic; - NSDate *lastUpdate; + BOOL quickUpdateFailed; + NSUInteger nextToCache; + NSDictionary *updateURLDic; + NSDate *lastUpdate; //Main Window: Search IBOutlet NSTextField *searchField; IBOutlet NSProgressIndicator *searchIndicator; IBOutlet NSArrayController *resultsController; + IBOutlet NSTableView *searchResultsTable; NSMutableArray *searchResultsArray; NSTask *searchTask; NSPipe *searchPipe; @@ -63,7 +59,7 @@ NSDictionary *radioFormats; IBOutlet NSProgressIndicator *pvrSearchIndicator; IBOutlet NSArrayController *pvrResultsController; IBOutlet NSArrayController *pvrQueueController; - IBOutlet NSPanel *pvrPanel; + IBOutlet NSPanel *pvrPanel; NSMutableArray *pvrSearchResultsArray; NSTask *pvrSearchTask; NSPipe *pvrSearchPipe; @@ -91,14 +87,14 @@ NSDictionary *radioFormats; //Preferences NSMutableArray *tvFormatList; NSMutableArray *radioFormatList; - NSMutableArray *itvFormatList; + NSMutableArray *itvFormatList; IBOutlet NSArrayController *tvFormatController; IBOutlet NSArrayController *radioFormatController; - IBOutlet NSArrayController *itvFormatController; - IBOutlet NSButton *itvTVCheckbox; - IBOutlet NSPanel *prefsPanel; - IBOutlet NSButton *ch4TVCheckbox; - + IBOutlet NSArrayController *itvFormatController; + IBOutlet NSButton *itvTVCheckbox; + IBOutlet NSPanel *prefsPanel; + IBOutlet NSButton *ch4TVCheckbox; + //Scheduling a Start IBOutlet NSPanel *scheduleWindow; IBOutlet NSDatePicker *datePicker; @@ -116,29 +112,51 @@ NSDictionary *radioFormats; NSTask *mplayerStreamer; NSPipe *liveTVPipe; NSPipe *liveTVError; - - //Download Solutions - IBOutlet NSWindow *solutionsWindow; - IBOutlet NSArrayController *solutionsArrayController; - IBOutlet NSTableView *solutionsTableView; - NSDictionary *solutionsDictionary; - - //Proxy - HTTPProxy *proxy; - NSMutableDictionary *proxyDict; - enum { - kProxyLoadCancelled = 1, - kProxyLoadFailed = 2, - kProxyTestFailed = 3 - }; - - //PVR list editing - NilToStringTransformer *nilToEmptyStringTransformer; - NilToStringTransformer *nilToAsteriskTransformer; - - //Verbose Logging - BOOL verbose; + + //Download Solutions + IBOutlet NSWindow *solutionsWindow; + IBOutlet NSArrayController *solutionsArrayController; + IBOutlet NSTableView *solutionsTableView; + NSDictionary *solutionsDictionary; + + //Proxy + HTTPProxy *proxy; + NSMutableDictionary *proxyDict; + enum { + kProxyLoadCancelled = 1, + kProxyLoadFailed = 2, + kProxyTestFailed = 3 + }; + + + //PVR list editing + NilToStringTransformer *nilToEmptyStringTransformer; + NilToStringTransformer *nilToAsteriskTransformer; + + //Verbose Logging + BOOL verbose; + + //Extended Show Information + IBOutlet NSProgressIndicator *retrievingInfoIndicator; + IBOutlet NSTextField *loadingLabel; + IBOutlet NSView *loadingView; + IBOutlet NSView *infoView; + IBOutlet NSPopover *popover; + IBOutlet NSImageView *imageView; + IBOutlet NSTextField *seriesName; + IBOutlet NSTextField *episodeName; + IBOutlet NSTextField *numbersField; + IBOutlet NSTextField *durationField; + IBOutlet NSTextField *categoriesField; + IBOutlet NSTextField *firstBroadcastField; + IBOutlet NSTextField *lastBroadcastField; + IBOutlet NSTextView *descriptionView; + IBOutlet NSDictionaryController *modeSizeController; } + +//Proxy +- (void)loadProxyInBackgroundForSelector:(SEL)selector withObject:(id)object; + //Update - (void)getiPlayerUpdateFinished; - (IBAction)updateCache:(id)sender; @@ -165,6 +183,7 @@ NSDictionary *radioFormats; - (IBAction)restoreDefaults:(id)sender; - (IBAction)showFeedback:(id)sender; - (IBAction)closeWindow:(id)sender; ++ (AppController*)sharedController; //Queue - (IBAction)addToQueue:(id)sender; @@ -200,11 +219,17 @@ NSDictionary *radioFormats; - (IBAction)startLiveTV:(id)sender; - (IBAction)stopLiveTV:(id)sender; +//Extended Show Information +- (IBAction)showExtendedInformationForSelectedProgramme:(id)sender; +- (void)loadProxyInBackgroundForSelector:(SEL)selector withObject:(id)object onTarget:(id)target; + + //Download Solutions //- (IBAction)saveSolutionsAsText:(id)sender; //Key-Value Coding @property (readwrite) NSMutableAttributedString *log_value; @property (readonly) NSString *getiPlayerPath; +@property (readonly) HTTPProxy *proxy; @end diff --git a/AppController.m b/AppController.m index 83022b10..2dc1eee8 100644 --- a/AppController.m +++ b/AppController.m @@ -19,15 +19,23 @@ #import "Chrome.h" #import "ASIHTTPRequest.h" +static AppController *sharedController; +bool runDownloads=NO; +bool runUpdate=NO; +NSDictionary *tvFormats; +NSDictionary *radioFormats; + @implementation AppController #pragma mark Overriden Methods - (id)description { return @"AppController"; } -- (id)init { +- (id)init { //Initialization if (!(self = [super init])) return nil; + sharedController = self; + NSNotificationCenter *nc; nc = [NSNotificationCenter defaultCenter]; @@ -38,21 +46,21 @@ - (id)init { queueArray = [NSMutableArray array]; //Initialize Log - NSString *version = [NSString stringWithFormat:@"%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]; - NSLog(@"Get iPlayer Automator %@ Initialized.", version); + NSString *version = [NSString stringWithFormat:@"%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]; + NSLog(@"Get iPlayer Automator %@ Initialized.", version); log_value = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"Get iPlayer Automator %@ Initialized.", version]]; [self addToLog:@"" :nil]; [nc addObserver:self selector:@selector(addToLogNotification:) name:@"AddToLog" object:nil]; [nc addObserver:self selector:@selector(postLog:) name:@"NeedLog" object:nil]; - - //Look for Start notifications for ASS - [nc addObserver:self selector:@selector(applescriptStartDownloads) name:@"StartDownloads" object:nil]; + + //Look for Start notifications for ASS + [nc addObserver:self selector:@selector(applescriptStartDownloads) name:@"StartDownloads" object:nil]; //Register Default Preferences NSMutableDictionary *defaultValues = [[NSMutableDictionary alloc] init]; - NSString *defaultDownloadDirectory = @"~/Movies/TV Shows"; + NSString *defaultDownloadDirectory = @"~/Movies/TV Shows"; defaultValues[@"DownloadPath"] = [defaultDownloadDirectory stringByExpandingTildeInPath]; defaultValues[@"Proxy"] = @"Provided"; defaultValues[@"CustomProxy"] = @""; @@ -74,14 +82,14 @@ - (id)init { defaultValues[@"XBMC_naming"] = @NO; defaultValues[@"KeepSeriesFor"] = @"30"; defaultValues[@"RemoveOldSeries"] = @NO; - defaultValues[@"AudioDescribed"] = @NO; - defaultValues[@"QuickCache"] = @YES; - defaultValues[@"TagShows"] = @YES; - // TODO: remove 4oD - // set 4oD off by default - defaultValues[@"Cache4oD_TV"] = @NO; - defaultValues[@"TestProxy"] = @YES; - defaultValues[@"ShowDownloadedInSearch"] = @YES; + defaultValues[@"AudioDescribed"] = @NO; + defaultValues[@"QuickCache"] = @YES; + defaultValues[@"TagShows"] = @YES; + // TODO: remove 4oD + // set 4oD off by default + defaultValues[@"Cache4oD_TV"] = @NO; + defaultValues[@"TestProxy"] = @YES; + defaultValues[@"ShowDownloadedInSearch"] = @YES; [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues]; defaultValues = nil; @@ -116,53 +124,58 @@ - (id)init { getiPlayerPath = [[NSString alloc] initWithString:[[NSBundle mainBundle] bundlePath]]; getiPlayerPath = [getiPlayerPath stringByAppendingString:@"/Contents/Resources/get_iplayer.pl"]; runScheduled=NO; - quickUpdateFailed=NO; - proxyDict = [[NSMutableDictionary alloc] init]; - nilToEmptyStringTransformer = [[NilToStringTransformer alloc] init]; - nilToAsteriskTransformer = [[NilToStringTransformer alloc] initWithString:@"*"]; - [NSValueTransformer setValueTransformer:nilToEmptyStringTransformer forName:@"NilToEmptyStringTransformer"]; - [NSValueTransformer setValueTransformer:nilToAsteriskTransformer forName:@"NilToAsteriskTransformer"]; - verbose = [[NSUserDefaults standardUserDefaults] boolForKey:@"Verbose"]; - return self; + quickUpdateFailed=NO; + proxyDict = [[NSMutableDictionary alloc] init]; + nilToEmptyStringTransformer = [[NilToStringTransformer alloc] init]; + nilToAsteriskTransformer = [[NilToStringTransformer alloc] initWithString:@"*"]; + [NSValueTransformer setValueTransformer:nilToEmptyStringTransformer forName:@"NilToEmptyStringTransformer"]; + [NSValueTransformer setValueTransformer:nilToAsteriskTransformer forName:@"NilToAsteriskTransformer"]; + verbose = [[NSUserDefaults standardUserDefaults] boolForKey:@"Verbose"]; + return self; } #pragma mark Delegate Methods - (void)awakeFromNib { #ifdef __x86_64__ - [itvTVCheckbox setEnabled:YES]; + [itvTVCheckbox setEnabled:YES]; #else - [itvTVCheckbox setEnabled:NO]; - [itvTVCheckbox setState:NSOffState]; - [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO] forKey:@"CacheITV_TV"]; + [itvTVCheckbox setEnabled:NO]; + [itvTVCheckbox setState:NSOffState]; + [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO] forKey:@"CacheITV_TV"]; #endif - + + //Initialize Search Results Click Actions + [searchResultsTable setTarget:self]; + [searchResultsTable setDoubleAction:@selector(addToQueue:)]; + + //Read Queue & Series-Link from File NSFileManager *fileManager = [NSFileManager defaultManager]; - + NSString *folder = @"~/Library/Application Support/Get iPlayer Automator/"; folder = [folder stringByExpandingTildeInPath]; if ([fileManager fileExistsAtPath: folder] == NO) { [fileManager createDirectoryAtPath:folder withIntermediateDirectories:NO attributes:nil error:nil]; } - - // TODO: remove 4oD - // disable 4oD and delete CH4 cache - [[NSUserDefaults standardUserDefaults] setValue:@NO forKey:@"Cache4oD_TV"]; - [ch4TVCheckbox setState:NSOffState]; - [ch4TVCheckbox setEnabled:NO]; - [fileManager removeItemAtPath:[folder stringByAppendingPathComponent:@"ch4.cache"] error:nil]; - + + // TODO: remove 4oD + // disable 4oD and delete CH4 cache + [[NSUserDefaults standardUserDefaults] setValue:@NO forKey:@"Cache4oD_TV"]; + [ch4TVCheckbox setState:NSOffState]; + [ch4TVCheckbox setEnabled:NO]; + [fileManager removeItemAtPath:[folder stringByAppendingPathComponent:@"ch4.cache"] error:nil]; + NSString *filename = @"Queue.automatorqueue"; NSString *filePath = [folder stringByAppendingPathComponent:filename]; NSDictionary * rootObject; - @try + @try { rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; NSArray *tempQueue = [rootObject valueForKey:@"queue"]; NSArray *tempSeries = [rootObject valueForKey:@"serieslink"]; - lastUpdate = [rootObject valueForKey:@"lastUpdate"]; + lastUpdate = [rootObject valueForKey:@"lastUpdate"]; [queueController addObjects:tempQueue]; [pvrQueueController addObjects:tempSeries]; } @@ -178,7 +191,7 @@ - (void)awakeFromNib filename = @"Formats.automatorqueue"; filePath = [folder stringByAppendingPathComponent:filename]; - @try + @try { rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; [radioFormatController addObjects:[rootObject valueForKey:@"radioFormats"]]; @@ -190,37 +203,37 @@ - (void)awakeFromNib NSLog(@"Unable to load saved application data. Deleted the data file."); rootObject=nil; } - if (!tvFormats || !radioFormats) { - [BBCDownload initFormats]; - } - // clear obsolete formats - NSMutableArray *tempTVFormats = [[NSMutableArray alloc] initWithArray:[tvFormatController arrangedObjects]]; - for (TVFormat *tvFormat in tempTVFormats) { - if (!tvFormats[[tvFormat format]]) { - [tvFormatController removeObject:tvFormat]; - } - } - NSMutableArray *tempRadioFormats = [[NSMutableArray alloc] initWithArray:[radioFormatController arrangedObjects]]; - for (RadioFormat *radioFormat in tempRadioFormats) { - if (!radioFormats[[radioFormat format]]) { - [radioFormatController removeObject:radioFormat]; - } - } - - // TODO: Remove 4oD - BOOL hasCached4oD = [[rootObject valueForKey:@"hasUpdatedCacheFor4oD"] boolValue]; - - filename = @"ITVFormats.automator"; - filePath = [folder stringByAppendingPathComponent:filename]; - @try { - rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - [itvFormatController addObjects:[rootObject valueForKey:@"itvFormats"]]; - } - @catch (NSException *exception) { - [fileManager removeItemAtPath:filePath error:nil]; - rootObject=nil; - } - + if (!tvFormats || !radioFormats) { + [BBCDownload initFormats]; + } + // clear obsolete formats + NSMutableArray *tempTVFormats = [[NSMutableArray alloc] initWithArray:[tvFormatController arrangedObjects]]; + for (TVFormat *tvFormat in tempTVFormats) { + if (!tvFormats[[tvFormat format]]) { + [tvFormatController removeObject:tvFormat]; + } + } + NSMutableArray *tempRadioFormats = [[NSMutableArray alloc] initWithArray:[radioFormatController arrangedObjects]]; + for (RadioFormat *radioFormat in tempRadioFormats) { + if (!radioFormats[[radioFormat format]]) { + [radioFormatController removeObject:radioFormat]; + } + } + + // TODO: Remove 4oD + BOOL hasCached4oD = [[rootObject valueForKey:@"hasUpdatedCacheFor4oD"] boolValue]; + + filename = @"ITVFormats.automator"; + filePath = [folder stringByAppendingPathComponent:filename]; + @try { + rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + [itvFormatController addObjects:[rootObject valueForKey:@"itvFormats"]]; + } + @catch (NSException *exception) { + [fileManager removeItemAtPath:filePath error:nil]; + rootObject=nil; + } + //Adds Defaults to Type Preferences if ([[tvFormatController arrangedObjects] count] == 0) { @@ -240,28 +253,28 @@ - (void)awakeFromNib [format3 setFormat:@"Flash - MP3"]; [radioFormatController addObjects:@[format1,format2,format3]]; } - if ([[itvFormatController arrangedObjects] count] == 0) - { - TVFormat *format1 = [[TVFormat alloc] init]; - [format1 setFormat:@"Flash - High"]; - TVFormat *format2 = [[TVFormat alloc] init]; - [format2 setFormat:@"Flash - Standard"]; - TVFormat *format3 = [[TVFormat alloc] init]; - [format3 setFormat:@"Flash - Low"]; - TVFormat *format4 = [[TVFormat alloc] init]; - [format4 setFormat:@"Flash - Very Low"]; - [itvFormatController addObjects:@[format1,format2,format3,format4]]; - } - + if ([[itvFormatController arrangedObjects] count] == 0) + { + TVFormat *format1 = [[TVFormat alloc] init]; + [format1 setFormat:@"Flash - High"]; + TVFormat *format2 = [[TVFormat alloc] init]; + [format2 setFormat:@"Flash - Standard"]; + TVFormat *format3 = [[TVFormat alloc] init]; + [format3 setFormat:@"Flash - Low"]; + TVFormat *format4 = [[TVFormat alloc] init]; + [format4 setFormat:@"Flash - Very Low"]; + [itvFormatController addObjects:@[format1,format2,format3,format4]]; + } + //Growl Initialization - @try { - [GrowlApplicationBridge setGrowlDelegate:@""]; - } - @catch (NSException *e) { - NSLog(@"ERROR: Growl initialisation failed: %@: %@", [e name], [e description]); - [self addToLog:[NSString stringWithFormat:@"ERROR: Growl initialisation failed: %@: %@", [e name], [e description]]]; - } - + @try { + [GrowlApplicationBridge setGrowlDelegate:@""]; + } + @catch (NSException *e) { + NSLog(@"ERROR: Growl initialisation failed: %@: %@", [e name], [e description]); + [self addToLog:[NSString stringWithFormat:@"ERROR: Growl initialisation failed: %@: %@", [e name], [e description]]]; + } + //Populate Live TV Channel List LiveTVChannel *bbcOne = [[LiveTVChannel alloc] initWithChannelName:@"BBC One"]; LiveTVChannel *bbcTwo = [[LiveTVChannel alloc] initWithChannelName:@"BBC Two"]; @@ -273,13 +286,13 @@ - (void)awakeFromNib NSString *infoPath = @"~/.swfinfo"; infoPath = [infoPath stringByExpandingTildeInPath]; if ([fileManager fileExistsAtPath:infoPath]) [fileManager removeItemAtPath:infoPath error:nil]; - - if (hasCached4oD) - [self updateCache:nil]; - else - [self updateCache:@""]; - // ensure get_iplayer encodes output as UTF-8 - setenv("PERL_UNICODE", "S", 1); + + if (hasCached4oD) + [self updateCache:nil]; + else + [self updateCache:@""]; + // ensure get_iplayer encodes output as UTF-8 + setenv("PERL_UNICODE", "S", 1); } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)application { @@ -287,29 +300,29 @@ - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)applica } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (runDownloads) - { - NSAlert *downloadAlert = [NSAlert alertWithMessageText:@"Are you sure you wish to quit?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@"You are currently downloading shows. If you quit, they will be cancelled."]; - NSInteger response = [downloadAlert runModal]; - if (response == NSAlertDefaultReturn) return NSTerminateCancel; - } - else if (runUpdate) - { - NSAlert *updateAlert = [NSAlert alertWithMessageText:@"Are you sure?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@"Get iPlayer Automator is currently updating the cache." - @"If you proceed with quiting, some series-link information will be lost." - @"It is not reccommended to quit during an update. Are you sure you wish to quit?"]; - NSInteger response = [updateAlert runModal]; - if (response == NSAlertDefaultReturn) return NSTerminateCancel; - } - + if (runDownloads) + { + NSAlert *downloadAlert = [NSAlert alertWithMessageText:@"Are you sure you wish to quit?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@"You are currently downloading shows. If you quit, they will be cancelled."]; + NSInteger response = [downloadAlert runModal]; + if (response == NSAlertDefaultReturn) return NSTerminateCancel; + } + else if (runUpdate) + { + NSAlert *updateAlert = [NSAlert alertWithMessageText:@"Are you sure?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@"Get iPlayer Automator is currently updating the cache." + @"If you proceed with quiting, some series-link information will be lost." + @"It is not reccommended to quit during an update. Are you sure you wish to quit?"]; + NSInteger response = [updateAlert runModal]; + if (response == NSAlertDefaultReturn) return NSTerminateCancel; + } + return NSTerminateNow; } - (BOOL)windowShouldClose:(id)sender @@ -318,24 +331,24 @@ - (BOOL)windowShouldClose:(id)sender { if (runUpdate) { - NSAlert *updateAlert = [NSAlert alertWithMessageText:@"Are you sure?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@"Get iPlayer Automator is currently updating the cache." - @"If you proceed with quiting, some series-link information will be lost." - @"It is not reccommended to quit during an update. Are you sure you wish to quit?"]; + NSAlert *updateAlert = [NSAlert alertWithMessageText:@"Are you sure?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@"Get iPlayer Automator is currently updating the cache." + @"If you proceed with quiting, some series-link information will be lost." + @"It is not reccommended to quit during an update. Are you sure you wish to quit?"]; NSInteger response = [updateAlert runModal]; if (response == NSAlertDefaultReturn) return NO; else if (response == NSAlertAlternateReturn) return YES; } else if (runDownloads) { - NSAlert *downloadAlert = [NSAlert alertWithMessageText:@"Are you sure you wish to quit?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@"You are currently downloading shows. If you quit, they will be cancelled."]; + NSAlert *downloadAlert = [NSAlert alertWithMessageText:@"Are you sure you wish to quit?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@"You are currently downloading shows. If you quit, they will be cancelled."]; NSInteger response = [downloadAlert runModal]; if (response == NSAlertDefaultReturn) return NO; else return YES; @@ -354,213 +367,214 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification //End Downloads if Running if (runDownloads) [currentDownload cancelDownload:nil]; - - [self saveAppData]; + + [self saveAppData]; } - (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update { - @try - { - - [GrowlApplicationBridge notifyWithTitle:@"Update Available!" - description:[NSString stringWithFormat:@"Get iPlayer Automator %@ is available.",[update displayVersionString]] - notificationName:@"New Version Available" - iconData:nil - priority:0 - isSticky:NO - clickContext:nil]; - } - @catch (NSException *e) { - NSLog(@"ERROR: Growl notification failed (updater): %@: %@", [e name], [e description]); - [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (updater): %@: %@", [e name], [e description]]]; - } + @try + { + + [GrowlApplicationBridge notifyWithTitle:@"Update Available!" + description:[NSString stringWithFormat:@"Get iPlayer Automator %@ is available.",[update displayVersionString]] + notificationName:@"New Version Available" + iconData:nil + priority:0 + isSticky:NO + clickContext:nil]; + } + @catch (NSException *e) { + NSLog(@"ERROR: Growl notification failed (updater): %@: %@", [e name], [e description]); + [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (updater): %@: %@", [e name], [e description]]]; + } } #pragma mark Cache Update - (IBAction)updateCache:(id)sender { - @try - { - [searchField setEnabled:NO]; - [stopButton setEnabled:NO]; - [startButton setEnabled:NO]; - [pvrSearchField setEnabled:NO]; - } - @catch (NSException *e) { - NSLog(@"NO UI: updateCache:"); - } - if ((![[[NSUserDefaults standardUserDefaults] objectForKey:@"QuickCache"] boolValue] || quickUpdateFailed) && [[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) - { - [self loadProxyInBackgroundForSelector:@selector(updateCache:proxyError:) withObject:sender]; - } - else - { - [self updateCache:sender proxyError:nil]; - } + @try + { + [searchField setEnabled:NO]; + [stopButton setEnabled:NO]; + [startButton setEnabled:NO]; + [pvrSearchField setEnabled:NO]; + } + @catch (NSException *e) { + NSLog(@"NO UI: updateCache:"); + } + if ((![[[NSUserDefaults standardUserDefaults] objectForKey:@"QuickCache"] boolValue] || quickUpdateFailed) && [[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) + { + [self loadProxyInBackgroundForSelector:@selector(updateCache:proxyError:) withObject:sender]; + } + else + { + [self updateCache:sender proxyError:nil]; + } } - (void)updateCache:(id)sender proxyError:(NSError *)proxyError { - // reset after proxy load - @try - { - [searchField setEnabled:YES]; - [stopButton setEnabled:YES]; - [startButton setEnabled:YES]; - [pvrSearchField setEnabled:YES]; - } - @catch (NSException *e) { - NSLog(@"NO UI: updateCache:proxyError:"); - } - if ([proxyError code] == kProxyLoadCancelled) - return; + // reset after proxy load + @try + { + [searchField setEnabled:YES]; + [stopButton setEnabled:YES]; + [startButton setEnabled:YES]; + [pvrSearchField setEnabled:YES]; + } + @catch (NSException *e) { + NSLog(@"NO UI: updateCache:proxyError:"); + } + if ([proxyError code] == kProxyLoadCancelled) + return; runSinceChange=YES; runUpdate=YES; - didUpdate=NO; + didUpdate=NO; [mainWindow setDocumentEdited:YES]; NSArray *tempQueue = [queueController arrangedObjects]; for (Programme *show in tempQueue) { - if (![[show successful] isEqualToNumber:@NO]) + if (show.successful.boolValue) { [queueController removeObject:show]; } } - - //UI might not be loaded yet - @try - { - //Update Should Be Running: - [currentIndicator setIndeterminate:YES]; - [currentIndicator startAnimation:nil]; - [currentProgress setStringValue:@"Updating Program Indexes..."]; - //Shouldn't search until update is done. - [searchField setEnabled:NO]; - [stopButton setEnabled:NO]; - [startButton setEnabled:NO]; - [pvrSearchField setEnabled:NO]; - } - @catch (NSException *e) { - NSLog(@"NO UI"); - } - - if (![[[NSUserDefaults standardUserDefaults] objectForKey:@"QuickCache"] boolValue] || quickUpdateFailed) - { - quickUpdateFailed=NO; - - NSString *cacheExpiryArg; - if ([[sender class] isEqualTo:[@"" class]]) - { - cacheExpiryArg = @"-e1"; - } - else - { - cacheExpiryArg = [[NSString alloc] initWithFormat:@"-e%d", ([[[NSUserDefaults standardUserDefaults] objectForKey:@"CacheExpiryTime"] intValue]*3600)]; - } + + //UI might not be loaded yet + @try + { + //Update Should Be Running: + [currentIndicator setIndeterminate:YES]; + [currentIndicator startAnimation:nil]; + [currentProgress setStringValue:@"Updating Program Indexes..."]; + //Shouldn't search until update is done. + [searchField setEnabled:NO]; + [stopButton setEnabled:NO]; + [startButton setEnabled:NO]; + [pvrSearchField setEnabled:NO]; + } + @catch (NSException *e) { + NSLog(@"NO UI"); + } + + if (![[[NSUserDefaults standardUserDefaults] objectForKey:@"QuickCache"] boolValue] || quickUpdateFailed) + { + quickUpdateFailed=NO; + + NSString *cacheExpiryArg; + if ([[sender class] isEqualTo:[@"" class]]) + { + cacheExpiryArg = @"-e1"; + } + else + { + cacheExpiryArg = [[NSString alloc] initWithFormat:@"-e%d", ([[[NSUserDefaults standardUserDefaults] objectForKey:@"CacheExpiryTime"] intValue]*3600)]; + } + + NSString *typeArgument = [self typeArgument:nil]; + + getiPlayerUpdateArgs = @[getiPlayerPath,cacheExpiryArg,typeArgument,@"--nopurge",profileDirArg]; - NSString *typeArgument = [self typeArgument:nil]; - - NSString *proxyArg = NULL; - if (proxy && [[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) - { - proxyArg = [[NSString alloc] initWithFormat:@"-p%@", [proxy url]]; - } - - [self addToLog:@"Updating Program Index Feeds...\r" :self]; - - getiPlayerUpdateArgs = @[getiPlayerPath,cacheExpiryArg,typeArgument,@"--nopurge",profileDirArg,proxyArg]; - getiPlayerUpdateTask = [[NSTask alloc] init]; - [getiPlayerUpdateTask setLaunchPath:@"/usr/bin/perl"]; - [getiPlayerUpdateTask setArguments:getiPlayerUpdateArgs]; - getiPlayerUpdatePipe = [[NSPipe alloc] init]; - [getiPlayerUpdateTask setStandardOutput:getiPlayerUpdatePipe]; - [getiPlayerUpdateTask setStandardError:getiPlayerUpdatePipe]; - - NSFileHandle *fh = [getiPlayerUpdatePipe fileHandleForReading]; - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - - [nc addObserver:self - selector:@selector(dataReady:) - name:NSFileHandleReadCompletionNotification - object:fh]; - [getiPlayerUpdateTask launch]; - - [fh readInBackgroundAndNotify]; - } - else - { - [self addToLog:@"Updating Program Index Feeds from Server..." :nil]; - - NSLog(@"DEBUG: Last cache update: %@",lastUpdate); - - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - if (!lastUpdate || ([[NSDate date] timeIntervalSinceDate:lastUpdate] > ([[defaults objectForKey:@"CacheExpiryTime"] intValue]*3600)) || [[sender class] isEqualTo:[@"" class]]) - { - typesToCache = [[NSMutableArray alloc] initWithCapacity:5]; - if ([[defaults objectForKey:@"CacheBBC_TV"] boolValue]) [typesToCache addObject:@"tv"]; - if ([[defaults objectForKey:@"CacheITV_TV"] boolValue]) [typesToCache addObject:@"itv"]; - if ([[defaults objectForKey:@"CacheBBC_Radio"] boolValue]) [typesToCache addObject:@"radio"]; - if ([[defaults objectForKey:@"CacheBBC_Podcasts"] boolValue]) [typesToCache addObject:@"Podcast"]; - // TODO: Remove 4oD - if ([[defaults objectForKey:@"Cache4oD_TV"] boolValue]) [typesToCache addObject:@"ch4"]; - - NSArray *urlKeys = @[@"tv",@"itv",@"radio",@"podcast",@"ch4"]; - NSArray *urlObjects = @[@"http://tom-tech.com/get_iplayer/cache/tv.cache", - @"http://tom-tech.com/get_iplayer/cache/itv.cache", - @"http://tom-tech.com/get_iplayer/cache/radio.cache", - @"http://tom-tech.com/get_iplayer/cache/podcast.cache", - @"http://tom-tech.com/get_iplayer/cache/ch4.cache"]; - updateURLDic = [[NSDictionary alloc] initWithObjects:urlObjects forKeys:urlKeys]; - - nextToCache=0; - if ([typesToCache count] > 0) - [self updateCacheForType:typesToCache[0]]; - } - else [self getiPlayerUpdateFinished]; - - } + if (proxy && [[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) + { + getiPlayerUpdateArgs = [getiPlayerUpdateArgs arrayByAddingObject:[[NSString alloc] initWithFormat:@"-p%@", [proxy url]]]; + } + + [self addToLog:@"Updating Program Index Feeds...\r" :self]; + + + getiPlayerUpdateTask = [[NSTask alloc] init]; + [getiPlayerUpdateTask setLaunchPath:@"/usr/bin/perl"]; + [getiPlayerUpdateTask setArguments:getiPlayerUpdateArgs]; + getiPlayerUpdatePipe = [[NSPipe alloc] init]; + [getiPlayerUpdateTask setStandardOutput:getiPlayerUpdatePipe]; + [getiPlayerUpdateTask setStandardError:getiPlayerUpdatePipe]; + + NSFileHandle *fh = [getiPlayerUpdatePipe fileHandleForReading]; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc addObserver:self + selector:@selector(dataReady:) + name:NSFileHandleReadCompletionNotification + object:fh]; + [getiPlayerUpdateTask launch]; + + [fh readInBackgroundAndNotify]; + } + else + { + [self addToLog:@"Updating Program Index Feeds from Server..." :nil]; + + NSLog(@"DEBUG: Last cache update: %@",lastUpdate); + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if (!lastUpdate || ([[NSDate date] timeIntervalSinceDate:lastUpdate] > ([[defaults objectForKey:@"CacheExpiryTime"] intValue]*3600)) || [[sender class] isEqualTo:[@"" class]]) + { + typesToCache = [[NSMutableArray alloc] initWithCapacity:5]; + if ([[defaults objectForKey:@"CacheBBC_TV"] boolValue]) [typesToCache addObject:@"tv"]; + if ([[defaults objectForKey:@"CacheITV_TV"] boolValue]) [typesToCache addObject:@"itv"]; + if ([[defaults objectForKey:@"CacheBBC_Radio"] boolValue]) [typesToCache addObject:@"radio"]; + if ([[defaults objectForKey:@"CacheBBC_Podcasts"] boolValue]) [typesToCache addObject:@"podcast"]; + // TODO: Remove 4oD + if ([[defaults objectForKey:@"Cache4oD_TV"] boolValue]) [typesToCache addObject:@"ch4"]; + + NSArray *urlKeys = @[@"tv",@"itv",@"radio",@"podcast",@"ch4"]; + NSArray *urlObjects = @[@"http://tom-tech.com/get_iplayer/cache/tv.cache", + @"http://tom-tech.com/get_iplayer/cache/itv.cache", + @"http://tom-tech.com/get_iplayer/cache/radio.cache", + @"http://tom-tech.com/get_iplayer/cache/podcast.cache", + @"http://tom-tech.com/get_iplayer/cache/ch4.cache"]; + updateURLDic = [[NSDictionary alloc] initWithObjects:urlObjects forKeys:urlKeys]; + + nextToCache=0; + if ([typesToCache count] > 0) + [self updateCacheForType:typesToCache[0]]; + } + else [self getiPlayerUpdateFinished]; + + } } - (void)updateCacheForType:(NSString *)type { - [self addToLog:[NSString stringWithFormat:@" Retrieving %@ index feeds.",type] :nil]; - [currentProgress setStringValue:[NSString stringWithFormat:@"Updating Program Indexes: Getting %@ index feeds from server...",type]]; - - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:updateURLDic[type]]]; - [request setDelegate:self]; - [request setDidFinishSelector:@selector(indexRequestFinished:)]; - [request setDidFailSelector:@selector(indexRequestFinished:)]; - [request setTimeOutSeconds:10]; - [request setNumberOfTimesToRetryOnTimeout:2]; - [request setDownloadDestinationPath:[[@"~/Library/Application Support/Get iPlayer Automator" stringByExpandingTildeInPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.cache",type]]]; - [request startAsynchronous]; + [self addToLog:[NSString stringWithFormat:@" Retrieving %@ index feeds.",type] :nil]; + [currentProgress setStringValue:[NSString stringWithFormat:@"Updating Program Indexes: Getting %@ index feeds from server...",type]]; + + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:updateURLDic[type]]]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(indexRequestFinished:)]; + [request setDidFailSelector:@selector(indexRequestFinished:)]; + [request setTimeOutSeconds:10]; + [request setNumberOfTimesToRetryOnTimeout:2]; + [request setDownloadDestinationPath:[[@"~/Library/Application Support/Get iPlayer Automator" stringByExpandingTildeInPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.cache",type]]]; + [request startAsynchronous]; } - (void)indexRequestFinished:(ASIHTTPRequest *)request { - if ([request responseStatusCode] != 200) - { - quickUpdateFailed=YES; - [self updateCache:@""]; - } - else - { - didUpdate=YES; - nextToCache++; - if (nextToCache < [typesToCache count]) - [self updateCacheForType:typesToCache[nextToCache]]; - else - { - [self getiPlayerUpdateFinished]; - } - } + if ([request responseStatusCode] != 200) + { + quickUpdateFailed=YES; + [self updateCache:@""]; + } + else + { + didUpdate=YES; + nextToCache++; + if (nextToCache < [typesToCache count]) + [self updateCacheForType:typesToCache[nextToCache]]; + else + { + [self getiPlayerUpdateFinished]; + } + } } - (void)dataReady:(NSNotification *)n { - NSData *d; - d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; + NSData *d; + d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; BOOL matches=NO; - if ([d length] > 0) { + if ([d length] > 0) { NSString *s = [[NSString alloc] initWithData:d - encoding:NSUTF8StringEncoding]; + encoding:NSUTF8StringEncoding]; if ([s hasPrefix:@"INFO:"]) { [self addToLog:[NSString stringWithString:s] :nil]; @@ -572,10 +586,10 @@ - (void)dataReady:(NSNotification *)n infoMessage = nil; scanner = nil; } - else if ([s hasPrefix:@"WARNING:"] || [s hasPrefix:@"ERROR:"]) - { - [self addToLog:s :nil]; - } + else if ([s hasPrefix:@"WARNING:"] || [s hasPrefix:@"ERROR:"]) + { + [self addToLog:s :nil]; + } else if ([s isEqualToString:@"."]) { NSMutableString *infomessage = [[NSMutableString alloc] initWithFormat:@"%@.", [currentProgress stringValue]]; @@ -590,16 +604,16 @@ - (void)dataReady:(NSNotification *)n getiPlayerUpdateTask=nil; [self getiPlayerUpdateFinished]; } - } + } else { getiPlayerUpdateTask = nil; [self getiPlayerUpdateFinished]; } - // If the task is running, start reading again - if (getiPlayerUpdateTask && !matches) - [[getiPlayerUpdatePipe fileHandleForReading] readInBackgroundAndNotify]; + // If the task is running, start reading again + if (getiPlayerUpdateTask && !matches) + [[getiPlayerUpdatePipe fileHandleForReading] readInBackgroundAndNotify]; } - (void)getiPlayerUpdateFinished { @@ -614,26 +628,26 @@ - (void)getiPlayerUpdateFinished getiPlayerUpdateTask = nil; [startButton setEnabled:YES]; [pvrSearchField setEnabled:YES]; - + if (didUpdate) { - @try - { - [GrowlApplicationBridge notifyWithTitle:@"Index Updated" - description:@"The program index was updated." - notificationName:@"Index Updating Completed" - iconData:nil - priority:0 - isSticky:NO - clickContext:nil]; - } - @catch (NSException *e) { - NSLog(@"ERROR: Growl notification failed (getiPlayerUpdateFinished): %@: %@", [e name], [e description]); - [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (getiPlayerUpdateFinished): %@: %@", [e name], [e description]]]; - } + @try + { + [GrowlApplicationBridge notifyWithTitle:@"Index Updated" + description:@"The program index was updated." + notificationName:@"Index Updating Completed" + iconData:nil + priority:0 + isSticky:NO + clickContext:nil]; + } + @catch (NSException *e) { + NSLog(@"ERROR: Growl notification failed (getiPlayerUpdateFinished): %@: %@", [e name], [e description]); + [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (getiPlayerUpdateFinished): %@: %@", [e name], [e description]]]; + } [self addToLog:@"Index Updated." :self]; - lastUpdate=[NSDate date]; + lastUpdate=[NSDate date]; } else { @@ -649,101 +663,101 @@ - (void)getiPlayerUpdateFinished { BOOL foundMatch=NO; if ([[show showName] length] > 0) - { - NSTask *pipeTask = [[NSTask alloc] init]; - NSPipe *newPipe = [[NSPipe alloc] init]; - NSFileHandle *readHandle2 = [newPipe fileHandleForReading]; - NSData *someData; - - NSString *name = [[show showName] copy]; - NSScanner *scanner = [NSScanner scannerWithString:name]; - NSString *searchArgument; - [scanner scanUpToString:@" - " intoString:&searchArgument]; - // write handle is closed to this process - [pipeTask setStandardOutput:newPipe]; - [pipeTask setStandardError:newPipe]; - [pipeTask setLaunchPath:@"/usr/bin/perl"]; - [pipeTask setArguments:@[getiPlayerPath,profileDirArg,@"--nopurge",noWarningArg,[self typeArgument:nil],[self cacheExpiryArgument:nil],listFormat, - searchArgument]]; - NSMutableString *taskData = [[NSMutableString alloc] initWithString:@""]; - [pipeTask launch]; - while ((someData = [readHandle2 availableData]) && [someData length]) { - [taskData appendString:[[NSString alloc] initWithData:someData - encoding:NSUTF8StringEncoding]]; - } - NSString *string = [NSString stringWithString:taskData]; - NSUInteger length = [string length]; - NSUInteger paraStart = 0, paraEnd = 0, contentsEnd = 0; - NSMutableArray *array = [NSMutableArray array]; - NSRange currentRange; - while (paraEnd < length) { - [string getParagraphStart:¶Start end:¶End + { + NSTask *pipeTask = [[NSTask alloc] init]; + NSPipe *newPipe = [[NSPipe alloc] init]; + NSFileHandle *readHandle2 = [newPipe fileHandleForReading]; + NSData *someData; + + NSString *name = [[show showName] copy]; + NSScanner *scanner = [NSScanner scannerWithString:name]; + NSString *searchArgument; + [scanner scanUpToString:@" - " intoString:&searchArgument]; + // write handle is closed to this process + [pipeTask setStandardOutput:newPipe]; + [pipeTask setStandardError:newPipe]; + [pipeTask setLaunchPath:@"/usr/bin/perl"]; + [pipeTask setArguments:@[getiPlayerPath,profileDirArg,@"--nopurge",noWarningArg,[self typeArgument:nil],[self cacheExpiryArgument:nil],listFormat, + searchArgument]]; + NSMutableString *taskData = [[NSMutableString alloc] initWithString:@""]; + [pipeTask launch]; + while ((someData = [readHandle2 availableData]) && [someData length]) { + [taskData appendString:[[NSString alloc] initWithData:someData + encoding:NSUTF8StringEncoding]]; + } + NSString *string = [NSString stringWithString:taskData]; + NSUInteger length = [string length]; + NSUInteger paraStart = 0, paraEnd = 0, contentsEnd = 0; + NSMutableArray *array = [NSMutableArray array]; + NSRange currentRange; + while (paraEnd < length) { + [string getParagraphStart:¶Start end:¶End contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; - currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); - [array addObject:[string substringWithRange:currentRange]]; - } - for (NSString *string in array) - { - if (![string isEqualToString:@"Matches:"] && ![string hasPrefix:@"INFO:"] && ![string hasPrefix:@"WARNING:"] && [string length]>0) - { - @try - { - NSScanner *myScanner = [NSScanner scannerWithString:string]; - Programme *p = [[Programme alloc] init]; - NSString *temp_pid, *temp_showName, *temp_tvNetwork, *temp_type, *url; - [myScanner scanUpToString:@":" intoString:&temp_pid]; - [myScanner scanUpToString:@"," intoString:&temp_type]; - [myScanner scanString:@", ~" intoString:NULL]; - [myScanner scanUpToString:@"~," intoString:&temp_showName]; - [myScanner scanString:@"~," intoString:NULL]; - [myScanner scanUpToString:@"," intoString:&temp_tvNetwork]; - [myScanner scanString:@"," intoString:nil]; - [myScanner scanUpToString:@"kljkjkj" intoString:&url]; - - if ([temp_showName hasSuffix:@" - -"]) - { - NSString *temp_showName2; - NSScanner *dashScanner = [NSScanner scannerWithString:temp_showName]; - [dashScanner scanUpToString:@" - -" intoString:&temp_showName2]; - temp_showName = temp_showName2; - temp_showName = [temp_showName stringByAppendingFormat:@" - %@", temp_showName2]; - } - [p setValue:temp_pid forKey:@"pid"]; - [p setValue:temp_showName forKey:@"showName"]; - [p setValue:temp_tvNetwork forKey:@"tvNetwork"]; - [p setUrl:url]; - if ([temp_type isEqualToString:@"radio"]) [p setValue:@YES forKey:@"radio"]; - if ([[p showName] isEqualToString:[show showName]] || ([[p url] isEqualToString:[show url]] && [show url])) - { - [show setValue:[p pid] forKey:@"pid"]; - foundMatch=YES; - break; - } - } - @catch (NSException *e) { - NSAlert *searchException = [[NSAlert alloc] init]; - [searchException addButtonWithTitle:@"OK"]; - [searchException setMessageText:[NSString stringWithFormat:@"Invalid Output!"]]; - [searchException setInformativeText:@"Please check your query. Your query must not alter the output format of Get_iPlayer. (getiPlayerUpdateFinished)"]; - [searchException setAlertStyle:NSWarningAlertStyle]; - [searchException runModal]; - searchException = nil; - } - } - else - { - if ([string hasPrefix:@"Unknown option:"] || [string hasPrefix:@"Option"] || [string hasPrefix:@"Usage"]) - { - NSLog(@"Unknown Option"); - } - } - } - if (!foundMatch) - { - [show setValue:@"Not Currently Available" forKey:@"status"]; - [show setValue:@YES forKey:@"complete"]; - [show setValue:@NO forKey:@"successful"]; - } + currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); + [array addObject:[string substringWithRange:currentRange]]; + } + for (NSString *string in array) + { + if (![string isEqualToString:@"Matches:"] && ![string hasPrefix:@"INFO:"] && ![string hasPrefix:@"WARNING:"] && [string length]>0) + { + @try + { + NSScanner *myScanner = [NSScanner scannerWithString:string]; + Programme *p = [[Programme alloc] init]; + NSString *temp_pid, *temp_showName, *temp_tvNetwork, *temp_type, *url; + [myScanner scanUpToString:@":" intoString:&temp_pid]; + [myScanner scanUpToString:@"," intoString:&temp_type]; + [myScanner scanString:@", ~" intoString:NULL]; + [myScanner scanUpToString:@"~," intoString:&temp_showName]; + [myScanner scanString:@"~," intoString:NULL]; + [myScanner scanUpToString:@"," intoString:&temp_tvNetwork]; + [myScanner scanString:@"," intoString:nil]; + [myScanner scanUpToString:@"kljkjkj" intoString:&url]; + + if ([temp_showName hasSuffix:@" - -"]) + { + NSString *temp_showName2; + NSScanner *dashScanner = [NSScanner scannerWithString:temp_showName]; + [dashScanner scanUpToString:@" - -" intoString:&temp_showName2]; + temp_showName = temp_showName2; + temp_showName = [temp_showName stringByAppendingFormat:@" - %@", temp_showName2]; + } + [p setValue:temp_pid forKey:@"pid"]; + [p setValue:temp_showName forKey:@"showName"]; + [p setValue:temp_tvNetwork forKey:@"tvNetwork"]; + [p setUrl:url]; + if ([temp_type isEqualToString:@"radio"]) [p setValue:@YES forKey:@"radio"]; + if ([[p showName] isEqualToString:[show showName]] || ([[p url] isEqualToString:[show url]] && [show url])) + { + [show setValue:[p pid] forKey:@"pid"]; + foundMatch=YES; + break; + } + } + @catch (NSException *e) { + NSAlert *searchException = [[NSAlert alloc] init]; + [searchException addButtonWithTitle:@"OK"]; + [searchException setMessageText:[NSString stringWithFormat:@"Invalid Output!"]]; + [searchException setInformativeText:@"Please check your query. Your query must not alter the output format of Get_iPlayer. (getiPlayerUpdateFinished)"]; + [searchException setAlertStyle:NSWarningAlertStyle]; + [searchException runModal]; + searchException = nil; + } + } + else + { + if ([string hasPrefix:@"Unknown option:"] || [string hasPrefix:@"Option"] || [string hasPrefix:@"Usage"]) + { + NSLog(@"Unknown Option"); + } + } + } + if (!foundMatch) + { + [show setValue:@"Not Currently Available" forKey:@"status"]; + [show setValue:@YES forKey:@"complete"]; + [show setValue:@NO forKey:@"successful"]; + } } } @@ -756,7 +770,7 @@ - (void)getiPlayerUpdateFinished } else { - if (runScheduled) + if (runScheduled) { [self performSelectorOnMainThread:@selector(startDownloads:) withObject:self waitUntilDone:NO]; } @@ -793,7 +807,7 @@ - (void)postLog:(NSNotification *)note } -(void)addToLog:(NSString *)string { - [self addToLog:string :nil]; + [self addToLog:string :nil]; } -(void)addToLog:(NSString *)string :(id)sender { //Get Current Log @@ -823,8 +837,8 @@ -(void)addToLog:(NSString *)string :(id)sender { //Make the Text White. [current_log addAttribute:NSForegroundColorAttributeName - value:[NSColor whiteColor] - range:NSMakeRange(0, [current_log length])]; + value:[NSColor whiteColor] + range:NSMakeRange(0, [current_log length])]; //Update the log. [self setValue:current_log forKey:@"log_value"]; @@ -849,13 +863,13 @@ - (IBAction)copyLog:(id)sender } #pragma mark Search - (IBAction)goToSearch:(id)sender { - [mainWindow makeKeyAndOrderFront:self]; - [mainWindow makeFirstResponder:searchField]; + [mainWindow makeKeyAndOrderFront:self]; + [mainWindow makeFirstResponder:searchField]; } - (IBAction)mainSearch:(id)sender { - [searchField setEnabled:NO]; - + [searchField setEnabled:NO]; + NSString *searchTerms = [searchField stringValue]; if([searchTerms length] > 0) @@ -868,10 +882,10 @@ - (IBAction)mainSearch:(id)sender NSString *cacheExpiryArg = [self cacheExpiryArgument:nil]; NSString *typeArgument = [self typeArgument:nil]; NSArray *args = @[getiPlayerPath,noWarningArg,cacheExpiryArg,typeArgument,listFormat,@"--long",@"--nopurge",searchArgument,profileDirArg]; - - if (![[[NSUserDefaults standardUserDefaults] valueForKey:@"ShowDownloadedInSearch"] boolValue]) - args=[args arrayByAddingObject:@"--hide"]; - + + if (![[[NSUserDefaults standardUserDefaults] valueForKey:@"ShowDownloadedInSearch"] boolValue]) + args=[args arrayByAddingObject:@"--hide"]; + [searchTask setArguments:args]; [searchTask setStandardOutput:searchPipe]; @@ -880,13 +894,13 @@ - (IBAction)mainSearch:(id)sender NSNotificationCenter *nc; nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self - selector:@selector(searchDataReady:) - name:NSFileHandleReadCompletionNotification - object:fh]; + selector:@selector(searchDataReady:) + name:NSFileHandleReadCompletionNotification + object:fh]; [nc addObserver:self - selector:@selector(searchFinished:) - name:NSTaskDidTerminateNotification - object:searchTask]; + selector:@selector(searchFinished:) + name:NSTaskDidTerminateNotification + object:searchTask]; searchData = [[NSMutableString alloc] init]; [searchTask launch]; [searchIndicator startAnimation:nil]; @@ -896,12 +910,12 @@ - (IBAction)mainSearch:(id)sender - (void)searchDataReady:(NSNotification *)n { - NSData *d; - d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; + NSData *d; + d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; - if ([d length] > 0) { + if ([d length] > 0) { NSString *s = [[NSString alloc] initWithData:d - encoding:NSUTF8StringEncoding]; + encoding:NSUTF8StringEncoding]; [searchData appendString:s]; } else @@ -909,13 +923,13 @@ - (void)searchDataReady:(NSNotification *)n searchTask = nil; } - // If the task is running, start reading again - if (searchTask) - [[searchPipe fileHandleForReading] readInBackgroundAndNotify]; + // If the task is running, start reading again + if (searchTask) + [[searchPipe fileHandleForReading] readInBackgroundAndNotify]; } - (void)searchFinished:(NSNotification *)n { - [searchField setEnabled:YES]; + [searchField setEnabled:YES]; BOOL foundShow=NO; [resultsController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[resultsController arrangedObjects] count])]]; NSString *string = [NSString stringWithString:searchData]; @@ -925,7 +939,7 @@ - (void)searchFinished:(NSNotification *)n NSRange currentRange; while (paraEnd < length) { [string getParagraphStart:¶Start end:¶End - contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; + contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); [array addObject:[string substringWithRange:currentRange]]; } @@ -943,12 +957,12 @@ - (void)searchFinished:(NSNotification *)n [myScanner scanUpToString:@"~," intoString:&temp_showName]; [myScanner scanUpToCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:NULL]; [myScanner scanUpToString:@"," intoString:&temp_tvNetwork]; - [myScanner scanUpToCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:NULL]; - [myScanner scanUpToString:@"kjkjkj" intoString:&url]; - if (temp_pid == nil || temp_showName == nil || temp_tvNetwork == nil || temp_type == nil || url == nil) { - [self addToLog: [NSString stringWithFormat:@"WARNING: Skipped invalid search result: %@", string]]; - continue; - } + [myScanner scanUpToCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:NULL]; + [myScanner scanUpToString:@"kjkjkj" intoString:&url]; + if (temp_pid == nil || temp_showName == nil || temp_tvNetwork == nil || temp_type == nil || url == nil) { + [self addToLog: [NSString stringWithFormat:@"WARNING: Skipped invalid search result: %@", string]]; + continue; + } if ([temp_showName hasSuffix:@" - -"]) { NSString *temp_showName2; @@ -958,14 +972,14 @@ - (void)searchFinished:(NSNotification *)n temp_showName = [temp_showName stringByAppendingFormat:@" - %@", temp_showName2]; } Programme *p = [[Programme alloc] initWithInfo:nil pid:temp_pid programmeName:temp_showName network:temp_tvNetwork]; - [p setUrl:url]; + [p setUrl:url]; if ([temp_type isEqualToString:@"radio"]) { [p setValue:@YES forKey:@"radio"]; } - else if ([temp_type isEqualToString:@"podcast"]) - [p setPodcast:@YES]; - + else if ([temp_type isEqualToString:@"podcast"]) + [p setPodcast:@YES]; + [resultsController addObject:p]; foundShow=YES; } @@ -992,11 +1006,11 @@ - (void)searchFinished:(NSNotification *)n [searchIndicator stopAnimation:nil]; if (!foundShow) { - NSAlert *noneFound = [NSAlert alertWithMessageText:@"No Shows Found" - defaultButton:@"OK" - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"0 shows were found for your search terms. Please check your spelling!"]; + NSAlert *noneFound = [NSAlert alertWithMessageText:@"No Shows Found" + defaultButton:@"OK" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"0 shows were found for your search terms. Please check your spelling!"]; [noneFound runModal]; } } @@ -1021,7 +1035,7 @@ - (IBAction)addToQueue:(id)sender { if ([[show showName] isEqualToString:[queuedShow showName]] && [show pid] == [queuedShow pid]) add=NO; } - if (add) + if (add) { if (runDownloads) [show setValue:@"Waiting..." forKey:@"status"]; [queueController addObject:show]; @@ -1070,7 +1084,7 @@ - (void)processGetNameData:(NSString *)getNameData forProgramme:(Programme *)p NSRange currentRange; while (paraEnd < length) { [string getParagraphStart:¶Start end:¶End - contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; + contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); [array addObject:[string substringWithRange:currentRange]]; } @@ -1092,10 +1106,10 @@ - (void)processGetNameData:(NSString *)getNameData forProgramme:(Programme *)p [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&type]; [scanner scanUpToCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:NULL]; [scanner scanUpToString:@"," intoString:&showName]; - [scanner scanString:@"," intoString:nil]; - [scanner scanUpToString:@"|" intoString:&tvNetwork]; - [scanner scanString:@"|" intoString:nil]; - [scanner scanUpToString:@"|" intoString:&url]; + [scanner scanString:@"," intoString:nil]; + [scanner scanUpToString:@"|" intoString:&tvNetwork]; + [scanner scanString:@"|" intoString:nil]; + [scanner scanUpToString:@"|" intoString:&url]; scanner = nil; } @catch (NSException *e) { @@ -1112,42 +1126,42 @@ - (void)processGetNameData:(NSString *)getNameData forProgramme:(Programme *)p found=YES; [p setValue:showName forKey:@"showName"]; [p setValue:index forKey:@"pid"]; - [p setValue:tvNetwork forKey:@"tvNetwork"]; - [p setUrl:url]; + [p setValue:tvNetwork forKey:@"tvNetwork"]; + [p setUrl:url]; if ([type isEqualToString:@"radio"]) [p setValue:@YES forKey:@"radio"]; - else if ([type isEqualToString:@"podcast"]) [p setPodcast:@YES]; + else if ([type isEqualToString:@"podcast"]) [p setPodcast:@YES]; } else if ([wantedID isEqualToString:index]) { found=YES; [p setValue:showName forKey:@"showName"]; - [p setValue:tvNetwork forKey:@"tvNetwork"]; - [p setUrl:url]; + [p setValue:tvNetwork forKey:@"tvNetwork"]; + [p setUrl:url]; if ([type isEqualToString:@"radio"]) [p setValue:@YES forKey:@"radio"]; - else if ([type isEqualToString:@"podcast"]) [p setPodcast:@YES]; + else if ([type isEqualToString:@"podcast"]) [p setPodcast:@YES]; } } - + } if (!found) - { - if ([[p showName] isEqualToString:@""] || [[p showName] isEqualToString:@"Unknown: Not in Cache"]) - [p setValue:@"Unknown: Not in Cache" forKey:@"showName"]; - [p setProcessedPID:@NO]; - } + { + if ([[p showName] isEqualToString:@""] || [[p showName] isEqualToString:@"Unknown: Not in Cache"]) + [p setValue:@"Unknown: Not in Cache" forKey:@"showName"]; + [p setProcessedPID:@NO]; + } else [p setProcessedPID:@YES]; } - (IBAction)getCurrentWebpage:(id)sender { - NSString *newShowName=nil; + NSString *newShowName=nil; //Get Default Browser NSString *browser = [[NSUserDefaults standardUserDefaults] objectForKey:@"DefaultBrowser"]; //Prepare Pointer for URL NSString *url = nil; - NSString *source = nil; + NSString *source = nil; //Prepare Alert in Case the Browser isn't Open NSAlert *browserNotOpen = [[NSAlert alloc] init]; @@ -1170,43 +1184,43 @@ - (IBAction)getCurrentWebpage:(id)sender { for (SafariWindow *window in windows) { - SafariTab *tab = [window currentTab]; - if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || - [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/episode/"] || - [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || - [[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/console/"] || - [[tab URL] hasPrefix:@"http://bbc.co.uk/sport"]) - { - url = [NSString stringWithString:[tab URL]]; - NSScanner *nameScanner = [NSScanner scannerWithString:[tab name]]; - [nameScanner scanString:@"BBC iPlayer - " intoString:nil]; - [nameScanner scanString:@"BBC Sport - " intoString:nil]; - [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; - foundURL=YES; - } - else if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/programmes/"]) { - url = [NSString stringWithString:[tab URL]]; - NSScanner *nameScanner = [NSScanner scannerWithString:[tab name]]; - [nameScanner scanUpToString:@" - " intoString:nil]; - [nameScanner scanString:@" - " intoString:nil]; - [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; - foundURL=YES; - source = [Safari doJavaScript:@"document.documentElement.outerHTML" in:tab]; - } - else if ([[tab URL] hasPrefix:@"https://www.itv.com/itvplayer/"] || - [[tab URL] hasPrefix:@"http://www.channel4.com/programmes/"] || - [[tab URL] hasPrefix:@"http://ps3.channel4.com"]) - { - url = [NSString stringWithString:[tab URL]]; - source = [Safari doJavaScript:@"document.documentElement.outerHTML" in:tab]; - newShowName = [[[tab name] stringByReplacingOccurrencesOfString:@" | itvplayer" withString:@""] stringByReplacingOccurrencesOfString:@" - 4oD - Channel 4" withString:@""]; - foundURL=YES; - } + SafariTab *tab = [window currentTab]; + if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || + [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/episode/"] || + [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || + [[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/console/"] || + [[tab URL] hasPrefix:@"http://bbc.co.uk/sport"]) + { + url = [NSString stringWithString:[tab URL]]; + NSScanner *nameScanner = [NSScanner scannerWithString:[tab name]]; + [nameScanner scanString:@"BBC iPlayer - " intoString:nil]; + [nameScanner scanString:@"BBC Sport - " intoString:nil]; + [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; + foundURL=YES; + } + else if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/programmes/"]) { + url = [NSString stringWithString:[tab URL]]; + NSScanner *nameScanner = [NSScanner scannerWithString:[tab name]]; + [nameScanner scanUpToString:@" - " intoString:nil]; + [nameScanner scanString:@" - " intoString:nil]; + [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; + foundURL=YES; + source = [Safari doJavaScript:@"document.documentElement.outerHTML" in:tab]; + } + else if ([[tab URL] hasPrefix:@"https://www.itv.com/itvplayer/"] || + [[tab URL] hasPrefix:@"http://www.channel4.com/programmes/"] || + [[tab URL] hasPrefix:@"http://ps3.channel4.com"]) + { + url = [NSString stringWithString:[tab URL]]; + source = [Safari doJavaScript:@"document.documentElement.outerHTML" in:tab]; + newShowName = [[[tab name] stringByReplacingOccurrencesOfString:@" | itvplayer" withString:@""] stringByReplacingOccurrencesOfString:@" - 4oD - Channel 4" withString:@""]; + foundURL=YES; + } } if (foundURL==NO) { url = [NSString stringWithString:[[windows[0] currentTab] URL]]; - //Might be incorrect + //Might be incorrect } } else @@ -1227,7 +1241,7 @@ - (IBAction)getCurrentWebpage:(id)sender return; } } - else if ([browser isEqualToString:@"Chrome"]) + else if ([browser isEqualToString:@"Chrome"]) { BOOL foundURL=NO; ChromeApplication *Chrome = [SBApplication applicationWithBundleIdentifier:@"com.google.Chrome"]; @@ -1240,43 +1254,43 @@ - (IBAction)getCurrentWebpage:(id)sender { for (ChromeWindow *window in windows) { - ChromeTab *tab = [window activeTab]; - if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || - [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/episode/"] || - [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || - [[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/console/"] || - [[tab URL] hasPrefix:@"http://bbc.co.uk/sport"]) - { - url = [NSString stringWithString:[tab URL]]; - NSScanner *nameScanner = [NSScanner scannerWithString:[tab title]]; - [nameScanner scanString:@"BBC iPlayer - " intoString:nil]; - [nameScanner scanString:@"BBC Sport - " intoString:nil]; - [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; - foundURL=YES; - } - else if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/programmes/"]) { - url = [NSString stringWithString:[tab URL]]; - NSScanner *nameScanner = [NSScanner scannerWithString:[tab title]]; - [nameScanner scanUpToString:@" - " intoString:nil]; - [nameScanner scanString:@" - " intoString:nil]; - [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; - foundURL=YES; - source = [tab executeJavascript:@"document.documentElement.outerHTML"]; - } - else if ([[tab URL] hasPrefix:@"https://www.itv.com/itvplayer/"] || - [[tab URL] hasPrefix:@"http://www.channel4.com/programmes/"] || - [[tab URL] hasPrefix:@"http://ps3.channel4.com"]) - { - url = [NSString stringWithString:[tab URL]]; - source = [tab executeJavascript:@"document.documentElement.outerHTML"]; - newShowName = [[[tab title] stringByReplacingOccurrencesOfString:@" | itvplayer" withString:@""] stringByReplacingOccurrencesOfString:@" - 4oD - Channel 4" withString:@""]; - foundURL=YES; - } + ChromeTab *tab = [window activeTab]; + if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || + [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/episode/"] || + [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || + [[tab URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/console/"] || + [[tab URL] hasPrefix:@"http://bbc.co.uk/sport"]) + { + url = [NSString stringWithString:[tab URL]]; + NSScanner *nameScanner = [NSScanner scannerWithString:[tab title]]; + [nameScanner scanString:@"BBC iPlayer - " intoString:nil]; + [nameScanner scanString:@"BBC Sport - " intoString:nil]; + [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; + foundURL=YES; + } + else if ([[tab URL] hasPrefix:@"http://www.bbc.co.uk/programmes/"]) { + url = [NSString stringWithString:[tab URL]]; + NSScanner *nameScanner = [NSScanner scannerWithString:[tab title]]; + [nameScanner scanUpToString:@" - " intoString:nil]; + [nameScanner scanString:@" - " intoString:nil]; + [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; + foundURL=YES; + source = [tab executeJavascript:@"document.documentElement.outerHTML"]; + } + else if ([[tab URL] hasPrefix:@"https://www.itv.com/itvplayer/"] || + [[tab URL] hasPrefix:@"http://www.channel4.com/programmes/"] || + [[tab URL] hasPrefix:@"http://ps3.channel4.com"]) + { + url = [NSString stringWithString:[tab URL]]; + source = [tab executeJavascript:@"document.documentElement.outerHTML"]; + newShowName = [[[tab title] stringByReplacingOccurrencesOfString:@" | itvplayer" withString:@""] stringByReplacingOccurrencesOfString:@" - 4oD - Channel 4" withString:@""]; + foundURL=YES; + } } if (foundURL==NO) { url = [NSString stringWithString:[[windows[0] activeTab] URL]]; - //Might be incorrect + //Might be incorrect } } else @@ -1298,151 +1312,151 @@ - (IBAction)getCurrentWebpage:(id)sender } } + else + { + [[NSAlert alertWithMessageText:@"Get iPlayer Automator currently only supports Safari and Chrome." defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Please change your preferred browser in the preferences and try again."] runModal]; + return; + } + /*else if ([browser isEqualToString:@"Firefox"]) + { + NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"GetFireFoxURL" ofType:@"applescript"]; + NSURL *scriptLocation = [[NSURL alloc] initFileURLWithPath:scriptPath]; + if (scriptLocation) + { + NSDictionary *errorDic; + NSAppleScript *getFirefoxURL = [[NSAppleScript alloc] initWithContentsOfURL:scriptLocation error:&errorDic]; + if (getFirefoxURL) + { + NSDictionary *executionError; + NSAppleEventDescriptor *result = [getFirefoxURL executeAndReturnError:&executionError]; + if (result) + { + url = [[NSString alloc] initWithString:[result stringValue]]; + if ([url isEqualToString:@"Error"]) + { + [browserNotOpen runModal]; + return; + } + } + } + } + } + else if ([browser isEqualToString:@"Camino"]) + { + //Scripting Bridge Version + CaminoApplication *camino = [SBApplication applicationWithBundleIdentifier:@"org.mozilla.camino"]; + if ([camino isRunning]) + { + BOOL foundURL=NO; + NSMutableArray *tabsArray = [[NSMutableArray alloc] init]; + SBElementArray *windows = [camino browserWindows]; + if ([[NSNumber numberWithUnsignedInteger:[windows count]] intValue]) + { + for (CaminoBrowserWindow *window in windows) + { + SBElementArray *tabs = [window tabs]; + if ([[NSNumber numberWithUnsignedInteger:[tabs count]] intValue]) + { + [tabsArray addObjectsFromArray:tabs]; + } + } + } + else [browserNotOpen runModal]; + if ([[NSNumber numberWithUnsignedInteger:[tabsArray count]] intValue]) + { + for (CaminoTab *tab in tabsArray) + { + if ([[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/episode/"] || [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || [[tab URL] hasPrefix:@"http://www.itv.com/ITVPlayer/Video/default.html?ViewType"]) + { + url = [[NSString alloc] initWithString:[tab URL]]; + foundURL=YES; + break; + } + } + if (foundURL==NO) + { + url = [[NSString alloc] initWithString:[[[tabsArray objectAtIndex:0] URL] path]]; + } + } else { - [[NSAlert alertWithMessageText:@"Get iPlayer Automator currently only supports Safari and Chrome." defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Please change your preferred browser in the preferences and try again."] runModal]; - return; + [browserNotOpen runModal]; + return; + } + } + else + { + [browserNotOpen runModal]; + return; + } } - /*else if ([browser isEqualToString:@"Firefox"]) - { - NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"GetFireFoxURL" ofType:@"applescript"]; - NSURL *scriptLocation = [[NSURL alloc] initFileURLWithPath:scriptPath]; - if (scriptLocation) - { - NSDictionary *errorDic; - NSAppleScript *getFirefoxURL = [[NSAppleScript alloc] initWithContentsOfURL:scriptLocation error:&errorDic]; - if (getFirefoxURL) - { - NSDictionary *executionError; - NSAppleEventDescriptor *result = [getFirefoxURL executeAndReturnError:&executionError]; - if (result) - { - url = [[NSString alloc] initWithString:[result stringValue]]; - if ([url isEqualToString:@"Error"]) - { - [browserNotOpen runModal]; - return; - } - } - } - } - } - else if ([browser isEqualToString:@"Camino"]) - { - //Scripting Bridge Version - CaminoApplication *camino = [SBApplication applicationWithBundleIdentifier:@"org.mozilla.camino"]; - if ([camino isRunning]) - { - BOOL foundURL=NO; - NSMutableArray *tabsArray = [[NSMutableArray alloc] init]; - SBElementArray *windows = [camino browserWindows]; - if ([[NSNumber numberWithUnsignedInteger:[windows count]] intValue]) - { - for (CaminoBrowserWindow *window in windows) - { - SBElementArray *tabs = [window tabs]; - if ([[NSNumber numberWithUnsignedInteger:[tabs count]] intValue]) - { - [tabsArray addObjectsFromArray:tabs]; - } - } - } - else [browserNotOpen runModal]; - if ([[NSNumber numberWithUnsignedInteger:[tabsArray count]] intValue]) - { - for (CaminoTab *tab in tabsArray) - { - if ([[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/episode/"] || [[tab URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || [[tab URL] hasPrefix:@"http://www.itv.com/ITVPlayer/Video/default.html?ViewType"]) - { - url = [[NSString alloc] initWithString:[tab URL]]; - foundURL=YES; - break; - } - } - if (foundURL==NO) - { - url = [[NSString alloc] initWithString:[[[tabsArray objectAtIndex:0] URL] path]]; - } - } - else - { - [browserNotOpen runModal]; - return; - } - } - else - { - [browserNotOpen runModal]; - return; - } - } else if ([browser isEqualToString:@"Chrome"]) { - NSLog(@"Beginning Chrome"); - BOOL foundURL=NO; - ChromeApplication *Chrome = [SBApplication applicationWithBundleIdentifier:@"com.google.Chrome"]; - if ([Chrome isRunning]) - { - NSLog(@"Chrome is running."); - @try - { - SBElementArray *windows = [Chrome windows]; - SBElementArray *tabs; - for (ChromeWindow *chromeWindow1 in windows) - { - [tabs addObject:[chromeWindow1 activeTab]]; - NSLog(@"Adding tab."); - } - if ([tabs count]>0) - { - NSLog(@"Have tabs"); - for (ChromeTab *document in tabs) - { - NSLog(@"Looking at tab"); - if ([[document URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || [[document URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || [[document URL] hasPrefix:@"http://www.itv.com/ITVPlayer/Video/default.html?ViewType"]) - { - url = [NSString stringWithString:[document URL]]; - NSScanner *nameScanner = [NSScanner scannerWithString:[document title]]; - [nameScanner scanString:@"BBC iPlayer - " intoString:nil]; - [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; - foundURL=YES; - } - } - if (foundURL==NO) - { - NSLog(@"Didn't Find URL"); - url = [NSString stringWithString:[[tabs objectAtIndex:0] URL]]; - //Might be incorrect - NSLog(@"%@", url); - } - else - { - NSLog(@"%@", url); - } - } - else - { - NSLog(@"Tab count is 0"); - for (ChromeWindow *chromeWindow in windows) - { - url=[[chromeWindow activeTab] URL]; - } - [browserNotOpen runModal]; - return; - } - } - @catch (NSException *e) - { - [browserNotOpen runModal]; - return; - } - } - else - { - [browserNotOpen runModal]; - return; - } - NSLog(@"%d", foundURL); + NSLog(@"Beginning Chrome"); + BOOL foundURL=NO; + ChromeApplication *Chrome = [SBApplication applicationWithBundleIdentifier:@"com.google.Chrome"]; + if ([Chrome isRunning]) + { + NSLog(@"Chrome is running."); + @try + { + SBElementArray *windows = [Chrome windows]; + SBElementArray *tabs; + for (ChromeWindow *chromeWindow1 in windows) + { + [tabs addObject:[chromeWindow1 activeTab]]; + NSLog(@"Adding tab."); + } + if ([tabs count]>0) + { + NSLog(@"Have tabs"); + for (ChromeTab *document in tabs) + { + NSLog(@"Looking at tab"); + if ([[document URL] hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || [[document URL] hasPrefix:@"http://bbc.co.uk/iplayer/console/"] || [[document URL] hasPrefix:@"http://www.itv.com/ITVPlayer/Video/default.html?ViewType"]) + { + url = [NSString stringWithString:[document URL]]; + NSScanner *nameScanner = [NSScanner scannerWithString:[document title]]; + [nameScanner scanString:@"BBC iPlayer - " intoString:nil]; + [nameScanner scanUpToString:@"kjklgfdjfgkdlj" intoString:&newShowName]; + foundURL=YES; + } + } + if (foundURL==NO) + { + NSLog(@"Didn't Find URL"); + url = [NSString stringWithString:[[tabs objectAtIndex:0] URL]]; + //Might be incorrect + NSLog(@"%@", url); + } + else + { + NSLog(@"%@", url); + } + } + else + { + NSLog(@"Tab count is 0"); + for (ChromeWindow *chromeWindow in windows) + { + url=[[chromeWindow activeTab] URL]; + } + [browserNotOpen runModal]; + return; + } + } + @catch (NSException *e) + { + [browserNotOpen runModal]; + return; + } + } + else + { + [browserNotOpen runModal]; + return; + } + NSLog(@"%d", foundURL); }*/ //Process URL if([url hasPrefix:@"http://www.bbc.co.uk/iplayer/episode/"] || [url hasPrefix:@"http://beta.bbc.co.uk/iplayer/episode"]) @@ -1460,7 +1474,7 @@ - (IBAction)getCurrentWebpage:(id)sender [urlScanner scanUpToString:@"/" intoString:&pid]; Programme *newProg = [[Programme alloc] init]; [newProg setValue:pid forKey:@"pid"]; - if (newShowName) [newProg setShowName:newShowName]; + if (newShowName) [newProg setShowName:newShowName]; [queueController addObject:newProg]; [self getNameForProgramme:newProg]; } @@ -1474,141 +1488,141 @@ - (IBAction)getCurrentWebpage:(id)sender [urlScanner scanString:@"/" intoString:nil]; [urlScanner scanUpToString:@"/" intoString:&pid]; NSScanner *scanner = [NSScanner scannerWithString:source]; - [scanner scanUpToString:[NSString stringWithFormat:@"emp.load(\"http://www.bbc.co.uk/iplayer/playlist/%@\")", pid] intoString:nil]; + [scanner scanUpToString:[NSString stringWithFormat:@"emp.load(\"http://www.bbc.co.uk/iplayer/playlist/%@\")", pid] intoString:nil]; if ([scanner isAtEnd]) { - NSAlert *invalidPage = [[NSAlert alloc] init]; - [invalidPage addButtonWithTitle:@"OK"]; - [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; - [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an iPlayer episode page."]; - [invalidPage setAlertStyle:NSWarningAlertStyle]; - [invalidPage runModal]; - return; - } + NSAlert *invalidPage = [[NSAlert alloc] init]; + [invalidPage addButtonWithTitle:@"OK"]; + [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; + [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an iPlayer episode page."]; + [invalidPage setAlertStyle:NSWarningAlertStyle]; + [invalidPage runModal]; + return; + } Programme *newProg = [[Programme alloc] init]; [newProg setValue:pid forKey:@"pid"]; - if (newShowName) [newProg setShowName:newShowName]; + if (newShowName) [newProg setShowName:newShowName]; [queueController addObject:newProg]; [self getNameForProgramme:newProg]; - } - else if ([url hasPrefix:@"http://www.bbc.co.uk/sport/olympics/2012/live-video/"]) - { - NSString *pid = nil; - NSScanner *urlScanner = [NSScanner scannerWithString:url]; - [urlScanner scanString:@"http://www.bbc.co.uk/sport/olympics/2012/live-video/" intoString:nil]; - [urlScanner scanUpToString:@"kfejklfjklj" intoString:&pid]; - [queueController addObject:[[Programme alloc] initWithInfo:nil pid:pid programmeName:newShowName network:@"BBC Sport"]]; - } + } + else if ([url hasPrefix:@"http://www.bbc.co.uk/sport/olympics/2012/live-video/"]) + { + NSString *pid = nil; + NSScanner *urlScanner = [NSScanner scannerWithString:url]; + [urlScanner scanString:@"http://www.bbc.co.uk/sport/olympics/2012/live-video/" intoString:nil]; + [urlScanner scanUpToString:@"kfejklfjklj" intoString:&pid]; + [queueController addObject:[[Programme alloc] initWithInfo:nil pid:pid programmeName:newShowName network:@"BBC Sport"]]; + } else if ([url hasPrefix:@"https://www.itv.com/itvplayer/"]) { - NSString *progname = nil, *productionId = nil, *pay_rights = nil, *title = nil, *action_type = nil; - progname = newShowName; + NSString *progname = nil, *productionId = nil, *pay_rights = nil, *title = nil, *action_type = nil; + progname = newShowName; NSScanner *scanner = [NSScanner scannerWithString:source]; - [scanner scanUpToString:@"\"productionId\":" intoString:nil]; - [scanner scanString:@"\"productionId\":\"" intoString:nil]; - [scanner scanUpToString:@"\"" intoString:&productionId]; - [scanner scanUpToString:@"\"action_type\":" intoString:nil]; - [scanner scanString:@"\"action_type\":\"" intoString:nil]; - [scanner scanUpToString:@"\"" intoString:&action_type]; - [scanner scanUpToString:@"\"pay_rights\":" intoString:nil]; - [scanner scanString:@"\"pay_rights\":\"" intoString:nil]; - [scanner scanUpToString:@"\"" intoString:&pay_rights]; - [scanner scanUpToString:@"

" intoString:nil]; - [scanner scanString:@">" intoString:nil]; - [scanner scanUpToString:@"<" intoString:&title]; - if (title) progname = title; - if (!progname || !productionId || (![pay_rights isEqualToString:@"free"] && ![action_type isEqualToString:@"free_taster"])) { - NSAlert *invalidPage = [[NSAlert alloc] init]; - [invalidPage addButtonWithTitle:@"OK"]; - [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; - [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an ITV Player free catch-up episode page."]; - [invalidPage setAlertStyle:NSWarningAlertStyle]; - [invalidPage runModal]; - return; - } - NSString *pid = [productionId stringByReplacingOccurrencesOfString:@"\\" withString:@""]; - NSString *showName = [NSString stringWithFormat:@"%@ - %@", progname, pid]; + [scanner scanUpToString:@"\"productionId\":" intoString:nil]; + [scanner scanString:@"\"productionId\":\"" intoString:nil]; + [scanner scanUpToString:@"\"" intoString:&productionId]; + [scanner scanUpToString:@"\"action_type\":" intoString:nil]; + [scanner scanString:@"\"action_type\":\"" intoString:nil]; + [scanner scanUpToString:@"\"" intoString:&action_type]; + [scanner scanUpToString:@"\"pay_rights\":" intoString:nil]; + [scanner scanString:@"\"pay_rights\":\"" intoString:nil]; + [scanner scanUpToString:@"\"" intoString:&pay_rights]; + [scanner scanUpToString:@"

" intoString:nil]; + [scanner scanString:@">" intoString:nil]; + [scanner scanUpToString:@"<" intoString:&title]; + if (title) progname = title; + if (!progname || !productionId || (![pay_rights isEqualToString:@"free"] && ![action_type isEqualToString:@"free_taster"])) { + NSAlert *invalidPage = [[NSAlert alloc] init]; + [invalidPage addButtonWithTitle:@"OK"]; + [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; + [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an ITV Player free catch-up episode page."]; + [invalidPage setAlertStyle:NSWarningAlertStyle]; + [invalidPage runModal]; + return; + } + NSString *pid = [productionId stringByReplacingOccurrencesOfString:@"\\" withString:@""]; + NSString *showName = [NSString stringWithFormat:@"%@ - %@", progname, pid]; Programme *newProg = [[Programme alloc] init]; - [newProg setPid:pid]; - [newProg setShowName:showName]; - [newProg setTvNetwork:@"ITV"]; - [newProg setProcessedPID:@YES]; - [newProg setUrl:url]; + [newProg setPid:pid]; + [newProg setShowName:showName]; + [newProg setTvNetwork:@"ITV"]; + [newProg setProcessedPID:@YES]; + [newProg setUrl:url]; [queueController addObject:newProg]; } -// TODO: remove 4oD -// disable 4oD -// else if ([url hasPrefix:@"http://www.channel4.com/programmes/"]) -// { -// NSString *pid = nil; -// NSScanner *urlScanner = [NSScanner scannerWithString:url]; -// [urlScanner scanUpToString:@"#" intoString:nil]; -// [urlScanner scanString:@"#" intoString:nil]; -// [urlScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:&pid]; -// if (!pid) { -// NSScanner *scanner = [NSScanner scannerWithString:source]; -// [scanner scanUpToString:@"data-assetid=\"" intoString:nil]; -// [scanner scanString:@"data-assetid=\"" intoString:nil]; -// [scanner scanUpToString:@"\"" intoString:&pid]; -// } -// if (!pid) -// { -// NSAlert *invalidPage = [[NSAlert alloc] init]; -// [invalidPage addButtonWithTitle:@"OK"]; -// [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; -// [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to a 4oD episode page."]; -// [invalidPage setAlertStyle:NSWarningAlertStyle]; -// [invalidPage runModal]; -// return; -// } -// Programme *newProg = [[Programme alloc] init]; -// [newProg setPid:pid]; -// [queueController addObject:newProg]; -// [self getNameForProgramme:newProg]; -// } -// else if ([url hasPrefix:@"http://ps3.channel4.com"]) -// { -// NSString *pid = nil, *seriesName = nil; -// NSScanner *ps3Scanner = [NSScanner scannerWithString:source]; -// [ps3Scanner scanUpToString:@"brandTitle=" intoString:nil]; -// [ps3Scanner scanString:@"brandTitle=" intoString:nil]; -// [ps3Scanner scanUpToString:@"&" intoString:&seriesName]; -// [ps3Scanner scanUpToString:@"preSelectAsset=" intoString:nil]; -// [ps3Scanner scanString:@"preSelectAsset=" intoString:nil]; -// [ps3Scanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:&pid]; -// if (!seriesName) seriesName = newShowName; -// if (!pid || !seriesName) -// { -// NSAlert *invalidPage = [[NSAlert alloc] init]; -// [invalidPage addButtonWithTitle:@"OK"]; -// [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; -// [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to a 4oD PS3 episode page."]; -// [invalidPage setAlertStyle:NSWarningAlertStyle]; -// [invalidPage runModal]; -// return; -// } -// NSString *showName = [NSString stringWithFormat:@"%@ - %@", seriesName, pid]; -// Programme *newProg = [[Programme alloc] init]; -// [newProg setPid:pid]; -// [newProg setShowName:showName]; -// [newProg setTvNetwork:@"4oD C4"]; -// [newProg setUrl:url]; -// [newProg setProcessedPID:@YES]; -// [queueController addObject:newProg]; -// } + // TODO: remove 4oD + // disable 4oD + // else if ([url hasPrefix:@"http://www.channel4.com/programmes/"]) + // { + // NSString *pid = nil; + // NSScanner *urlScanner = [NSScanner scannerWithString:url]; + // [urlScanner scanUpToString:@"#" intoString:nil]; + // [urlScanner scanString:@"#" intoString:nil]; + // [urlScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:&pid]; + // if (!pid) { + // NSScanner *scanner = [NSScanner scannerWithString:source]; + // [scanner scanUpToString:@"data-assetid=\"" intoString:nil]; + // [scanner scanString:@"data-assetid=\"" intoString:nil]; + // [scanner scanUpToString:@"\"" intoString:&pid]; + // } + // if (!pid) + // { + // NSAlert *invalidPage = [[NSAlert alloc] init]; + // [invalidPage addButtonWithTitle:@"OK"]; + // [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; + // [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to a 4oD episode page."]; + // [invalidPage setAlertStyle:NSWarningAlertStyle]; + // [invalidPage runModal]; + // return; + // } + // Programme *newProg = [[Programme alloc] init]; + // [newProg setPid:pid]; + // [queueController addObject:newProg]; + // [self getNameForProgramme:newProg]; + // } + // else if ([url hasPrefix:@"http://ps3.channel4.com"]) + // { + // NSString *pid = nil, *seriesName = nil; + // NSScanner *ps3Scanner = [NSScanner scannerWithString:source]; + // [ps3Scanner scanUpToString:@"brandTitle=" intoString:nil]; + // [ps3Scanner scanString:@"brandTitle=" intoString:nil]; + // [ps3Scanner scanUpToString:@"&" intoString:&seriesName]; + // [ps3Scanner scanUpToString:@"preSelectAsset=" intoString:nil]; + // [ps3Scanner scanString:@"preSelectAsset=" intoString:nil]; + // [ps3Scanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:&pid]; + // if (!seriesName) seriesName = newShowName; + // if (!pid || !seriesName) + // { + // NSAlert *invalidPage = [[NSAlert alloc] init]; + // [invalidPage addButtonWithTitle:@"OK"]; + // [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; + // [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to a 4oD PS3 episode page."]; + // [invalidPage setAlertStyle:NSWarningAlertStyle]; + // [invalidPage runModal]; + // return; + // } + // NSString *showName = [NSString stringWithFormat:@"%@ - %@", seriesName, pid]; + // Programme *newProg = [[Programme alloc] init]; + // [newProg setPid:pid]; + // [newProg setShowName:showName]; + // [newProg setTvNetwork:@"4oD C4"]; + // [newProg setUrl:url]; + // [newProg setProcessedPID:@YES]; + // [queueController addObject:newProg]; + // } else { NSAlert *invalidPage = [[NSAlert alloc] init]; [invalidPage addButtonWithTitle:@"OK"]; [invalidPage setMessageText:[NSString stringWithFormat:@"Invalid Page: %@",url]]; - // TODO: remove 4oD - // disable 4oD - // [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an iPlayer episode page, ITV Player free catch-up episode page, 4oD episode page or 4oD PS3 episode page."]; + // TODO: remove 4oD + // disable 4oD + // [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an iPlayer episode page, ITV Player free catch-up episode page, 4oD episode page or 4oD PS3 episode page."]; [invalidPage setInformativeText:@"Please ensure the frontmost browser tab is open to an iPlayer episode page or ITV Player free catch-up episode page. 4oD is no longer supported."]; [invalidPage setAlertStyle:NSWarningAlertStyle]; [invalidPage runModal]; } - + } - (IBAction)removeFromQueue:(id)sender { @@ -1626,12 +1640,12 @@ - (IBAction)removeFromQueue:(id)sender } if (downloading) { - NSAlert *cantRemove = [NSAlert alertWithMessageText:@"A Selected Show is Currently Downloading." - defaultButton:@"OK" - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"You can not remove a show that is currently downloading. " - @"Please stop the downloads then remove the download if you wish to cancel it."]; + NSAlert *cantRemove = [NSAlert alertWithMessageText:@"A Selected Show is Currently Downloading." + defaultButton:@"OK" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"You can not remove a show that is currently downloading. " + @"Please stop the downloads then remove the download if you wish to cancel it."]; [cantRemove runModal]; } else @@ -1660,51 +1674,50 @@ - (IBAction)hidePvrShow:(id)sender #pragma mark Download Controller - (IBAction)startDownloads:(id)sender { - @try - { - [stopButton setEnabled:NO]; - [startButton setEnabled:NO]; - } - @catch (NSException *e) { - NSLog(@"NO UI: startDownloads:"); - } - [self saveAppData]; //Save data in case of crash. - [self loadProxyInBackgroundForSelector:@selector(startDownloads:proxyError:) withObject:sender]; + @try + { + [stopButton setEnabled:NO]; + [startButton setEnabled:NO]; + } + @catch (NSException *e) { + NSLog(@"NO UI: startDownloads:"); + } + [self saveAppData]; //Save data in case of crash. + [self loadProxyInBackgroundForSelector:@selector(startDownloads:proxyError:) withObject:sender]; } - (void)startDownloads:(id)sender proxyError:(NSError *)proxyError { - // reset after proxy load - @try - { - [stopButton setEnabled:YES]; - [startButton setEnabled:YES]; - } - @catch (NSException *e) { - NSLog(@"NO UI: startDownloads:proxyError:"); - } - if ([proxyError code] == kProxyLoadCancelled) - return; + // reset after proxy load + @try + { + [stopButton setEnabled:YES]; + } + @catch (NSException *e) { + NSLog(@"NO UI: startDownloads:proxyError:"); + } + if ([proxyError code] == kProxyLoadCancelled) + return; NSAlert *whatAnIdiot = [NSAlert alertWithMessageText:@"No Shows in Queue!" - defaultButton:nil - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"Try adding shows to the queue before clicking start; " - @"Get iPlayer Automator needs to know what to download."]; + defaultButton:nil + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"Try adding shows to the queue before clicking start; " + @"Get iPlayer Automator needs to know what to download."]; if ([[queueController arrangedObjects] count] > 0) { - NSLog(@"Initialising Failure Dictionary"); - if (!solutionsDictionary) - solutionsDictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ReasonsForFailure" ofType:@"plist"]]; - NSLog(@"Failure Dictionary Ready"); - + NSLog(@"Initialising Failure Dictionary"); + if (!solutionsDictionary) + solutionsDictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ReasonsForFailure" ofType:@"plist"]]; + NSLog(@"Failure Dictionary Ready"); + BOOL foundOne=NO; runDownloads=YES; - runScheduled=NO; + runScheduled=NO; [mainWindow setDocumentEdited:YES]; [self addToLog:@"\rAppController: Starting Downloads" :nil]; - //Clean-Up Queue + //Clean-Up Queue NSArray *tempQueue = [queueController arrangedObjects]; for (Programme *show in tempQueue) { @@ -1742,31 +1755,31 @@ - (void)startDownloads:(id)sender proxyError:(NSError *)proxyError if (foundOne) { //Start First Download - IOPMAssertionCreateWithDescription(kIOPMAssertionTypePreventUserIdleSystemSleep, (CFStringRef)@"Downloading Show", (CFStringRef)@"GiA is downloading shows.", NULL, NULL, (double)0, NULL, &powerAssertionID); - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + IOPMAssertionCreateWithDescription(kIOPMAssertionTypePreventUserIdleSystemSleep, (CFStringRef)@"Downloading Show", (CFStringRef)@"GiA is downloading shows.", NULL, NULL, (double)0, NULL, &powerAssertionID); + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(setPercentage:) name:@"setPercentage" object:nil]; [nc addObserver:self selector:@selector(setProgress:) name:@"setCurrentProgress" object:nil]; [nc addObserver:self selector:@selector(nextDownload:) name:@"DownloadFinished" object:nil]; - + tempQueue = [queueController arrangedObjects]; [self addToLog:[NSString stringWithFormat:@"\rDownloading Show %lu/%lu:\r", - (unsigned long)1, - (unsigned long)[tempQueue count]] - :nil]; + (unsigned long)1, + (unsigned long)[tempQueue count]] + :nil]; for (Programme *show in tempQueue) { if ([[show complete] isEqualToNumber:@NO]) { - if ([[show tvNetwork] hasPrefix:@"ITV"]) - currentDownload = [[ITVDownload alloc] initWithProgramme:show itvFormats:[itvFormatController arrangedObjects] proxy:proxy]; - /*else if ([[show tvNetwork] hasPrefix:@"4oD"]) - currentDownload = [[FourODDownload alloc] initWithProgramme:show proxy:proxy];*/ - else - currentDownload = [[BBCDownload alloc] initWithProgramme:show - tvFormats:[tvFormatController arrangedObjects] - radioFormats:[radioFormatController arrangedObjects] - proxy:proxy]; + if ([[show tvNetwork] hasPrefix:@"ITV"]) + currentDownload = [[ITVDownload alloc] initWithProgramme:show itvFormats:[itvFormatController arrangedObjects] proxy:proxy]; + /*else if ([[show tvNetwork] hasPrefix:@"4oD"]) + currentDownload = [[FourODDownload alloc] initWithProgramme:show proxy:proxy];*/ + else + currentDownload = [[BBCDownload alloc] initWithProgramme:show + tvFormats:[tvFormatController arrangedObjects] + radioFormats:[radioFormatController arrangedObjects] + proxy:proxy]; break; } } @@ -1783,26 +1796,26 @@ - (void)startDownloads:(id)sender proxyError:(NSError *)proxyError } else { - runDownloads=NO; - [mainWindow setDocumentEdited:NO]; + runDownloads=NO; + [mainWindow setDocumentEdited:NO]; if (!runScheduled) - [whatAnIdiot runModal]; - else if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"AutoRetryFailed"] boolValue]) - { - NSDate *scheduledDate = [NSDate dateWithTimeIntervalSinceNow:60*[[[NSUserDefaults standardUserDefaults] valueForKey:@"AutoRetryTime"] doubleValue]]; - [datePicker setDateValue:scheduledDate]; - [self scheduleStart:self]; - } - else if (runScheduled) - runScheduled=NO; + [whatAnIdiot runModal]; + else if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"AutoRetryFailed"] boolValue]) + { + NSDate *scheduledDate = [NSDate dateWithTimeIntervalSinceNow:60*[[[NSUserDefaults standardUserDefaults] valueForKey:@"AutoRetryTime"] doubleValue]]; + [datePicker setDateValue:scheduledDate]; + [self scheduleStart:self]; + } + else if (runScheduled) + runScheduled=NO; } } - (IBAction)stopDownloads:(id)sender { - IOPMAssertionRelease(powerAssertionID); - + IOPMAssertionRelease(powerAssertionID); + runDownloads=NO; - runScheduled=NO; + runScheduled=NO; [currentDownload cancelDownload:self]; [[currentDownload show] setStatus:@"Cancelled"]; if (!runUpdate) @@ -1817,15 +1830,15 @@ - (IBAction)stopDownloads:(id)sender } NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc removeObserver:self name:@"setPercentage" object:nil]; - [nc removeObserver:self name:@"setCurrentProgress" object:nil]; - [nc removeObserver:self name:@"DownloadFinished" object:nil]; + [nc removeObserver:self name:@"setPercentage" object:nil]; + [nc removeObserver:self name:@"setCurrentProgress" object:nil]; + [nc removeObserver:self name:@"DownloadFinished" object:nil]; NSArray *tempQueue = [queueController arrangedObjects]; for (Programme *show in tempQueue) if ([[show status] isEqualToString:@"Waiting..."]) [show setStatus:@""]; - [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(fixDownloadStatus:) userInfo:currentDownload repeats:NO]; + [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(fixDownloadStatus:) userInfo:currentDownload repeats:NO]; } - (void)fixDownloadStatus:(NSNotification *)note { @@ -1839,14 +1852,14 @@ - (void)fixDownloadStatus:(NSNotification *)note NSLog(@"fixDownloadStatus handler did not run because downloads appear to be running again"); } - (void)setPercentage:(NSNotification *)note -{ +{ if ([note userInfo]) { NSDictionary *userInfo = [note userInfo]; [currentIndicator setIndeterminate:NO]; [currentIndicator startAnimation:nil]; - [currentIndicator setMinValue:0]; - [currentIndicator setMaxValue:100]; + [currentIndicator setMinValue:0]; + [currentIndicator setMaxValue:100]; [currentIndicator setDoubleValue:[[userInfo valueForKey:@"nsDouble"] doubleValue]]; } else @@ -1884,53 +1897,53 @@ - (void)nextDownload:(NSNotification *)note else [finishedShow setValue:@"Download Complete" forKey:@"status"]; - @try - { - [GrowlApplicationBridge notifyWithTitle:@"Download Finished" - description:[NSString stringWithFormat:@"%@ Completed Successfully",[finishedShow showName]] - notificationName:@"Download Finished" - iconData:nil - priority:0 - isSticky:NO - clickContext:nil]; - } - @catch (NSException *e) { - NSLog(@"ERROR: Growl notification failed (nextDownload - finished): %@: %@", [e name], [e description]); - [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (nextDownload - finished): %@: %@", [e name], [e description]]]; - } - } + @try + { + [GrowlApplicationBridge notifyWithTitle:@"Download Finished" + description:[NSString stringWithFormat:@"%@ Completed Successfully",[finishedShow showName]] + notificationName:@"Download Finished" + iconData:nil + priority:0 + isSticky:NO + clickContext:nil]; + } + @catch (NSException *e) { + NSLog(@"ERROR: Growl notification failed (nextDownload - finished): %@: %@", [e name], [e description]); + [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (nextDownload - finished): %@: %@", [e name], [e description]]]; + } + } else { - @try - { - [GrowlApplicationBridge notifyWithTitle:@"Download Failed" - description:[NSString stringWithFormat:@"%@ failed. See log for details.",[finishedShow showName]] - notificationName:@"Download Failed" - iconData:nil - priority:0 - isSticky:NO - clickContext:nil]; - } - @catch (NSException *e) { - NSLog(@"ERROR: Growl notification failed (nextDownload - failed): %@: %@", [e name], [e description]); - [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (nextDownload - failed): %@: %@", [e name], [e description]]]; - } - - ReasonForFailure *showSolution = [[ReasonForFailure alloc] init]; - [showSolution setShowName:[finishedShow showName]]; - [showSolution setSolution:[solutionsDictionary valueForKey:[finishedShow reasonForFailure]]]; - if (![showSolution solution]) - [showSolution setSolution:@"Problem Unknown.\nPlease submit a bug report from the application menu."]; - NSLog(@"Reason for Failure: %@", [finishedShow reasonForFailure]); - NSLog(@"Dictionary Lookup: %@", [solutionsDictionary valueForKey:[finishedShow reasonForFailure]]); - NSLog(@"Solution: %@", [showSolution solution]); - [solutionsArrayController addObject:showSolution]; - NSLog(@"Added Solution"); - [solutionsTableView setRowHeight:68]; + @try + { + [GrowlApplicationBridge notifyWithTitle:@"Download Failed" + description:[NSString stringWithFormat:@"%@ failed. See log for details.",[finishedShow showName]] + notificationName:@"Download Failed" + iconData:nil + priority:0 + isSticky:NO + clickContext:nil]; + } + @catch (NSException *e) { + NSLog(@"ERROR: Growl notification failed (nextDownload - failed): %@: %@", [e name], [e description]); + [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (nextDownload - failed): %@: %@", [e name], [e description]]]; + } + + ReasonForFailure *showSolution = [[ReasonForFailure alloc] init]; + [showSolution setShowName:[finishedShow showName]]; + [showSolution setSolution:[solutionsDictionary valueForKey:[finishedShow reasonForFailure]]]; + if (![showSolution solution]) + [showSolution setSolution:@"Problem Unknown.\nPlease submit a bug report from the application menu."]; + NSLog(@"Reason for Failure: %@", [finishedShow reasonForFailure]); + NSLog(@"Dictionary Lookup: %@", [solutionsDictionary valueForKey:[finishedShow reasonForFailure]]); + NSLog(@"Solution: %@", [showSolution solution]); + [solutionsArrayController addObject:showSolution]; + NSLog(@"Added Solution"); + [solutionsTableView setRowHeight:68]; } - - [self saveAppData]; //Save app data in case of crash. - + + [self saveAppData]; //Save app data in case of crash. + NSArray *tempQueue = [queueController arrangedObjects]; Programme *nextShow=nil; NSUInteger showNum=0; @@ -1951,27 +1964,27 @@ - (void)nextDownload:(NSNotification *)note [noneLeft raise]; } [self addToLog:[NSString stringWithFormat:@"\rDownloading Show %lu/%lu:\r", - (unsigned long)([tempQueue indexOfObject:nextShow]+1), - (unsigned long)[tempQueue count]] - :nil]; + (unsigned long)([tempQueue indexOfObject:nextShow]+1), + (unsigned long)[tempQueue count]] + :nil]; if ([[nextShow complete] isEqualToNumber:@NO]) - { - if ([[nextShow tvNetwork] hasPrefix:@"ITV"]) - currentDownload = [[ITVDownload alloc] initWithProgramme:nextShow itvFormats:[itvFormatController arrangedObjects] proxy:proxy]; - /*else if ([[nextShow tvNetwork] hasPrefix:@"4oD"]) - currentDownload = [[FourODDownload alloc] initWithProgramme:nextShow proxy:proxy];*/ - else - currentDownload = [[BBCDownload alloc] initWithProgramme:nextShow - tvFormats:[tvFormatController arrangedObjects] - radioFormats:[radioFormatController arrangedObjects] - proxy:proxy]; - } + { + if ([[nextShow tvNetwork] hasPrefix:@"ITV"]) + currentDownload = [[ITVDownload alloc] initWithProgramme:nextShow itvFormats:[itvFormatController arrangedObjects] proxy:proxy]; + /*else if ([[nextShow tvNetwork] hasPrefix:@"4oD"]) + currentDownload = [[FourODDownload alloc] initWithProgramme:nextShow proxy:proxy];*/ + else + currentDownload = [[BBCDownload alloc] initWithProgramme:nextShow + tvFormats:[tvFormatController arrangedObjects] + radioFormats:[radioFormatController arrangedObjects] + proxy:proxy]; + } } @catch (NSException *e) { //Downloads must be finished. - IOPMAssertionRelease(powerAssertionID); - + IOPMAssertionRelease(powerAssertionID); + [stopButton setEnabled:NO]; [startButton setEnabled:YES]; [currentProgress setStringValue:@""]; @@ -1981,9 +1994,9 @@ - (void)nextDownload:(NSNotification *)note [currentIndicator setIndeterminate:NO]; [self addToLog:@"\rAppController: Downloads Finished" :nil]; NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc removeObserver:self name:@"setPercentage" object:nil]; - [nc removeObserver:self name:@"setCurrentProgress" object:nil]; - [nc removeObserver:self name:@"DownloadFinished" object:nil]; + [nc removeObserver:self name:@"setPercentage" object:nil]; + [nc removeObserver:self name:@"setCurrentProgress" object:nil]; + [nc removeObserver:self name:@"DownloadFinished" object:nil]; runDownloads=NO; [mainWindow setDocumentEdited:NO]; @@ -2002,25 +2015,25 @@ - (void)nextDownload:(NSNotification *)note } } tempQueue=nil; - @try - { - [GrowlApplicationBridge notifyWithTitle:@"Downloads Finished" - description:[NSString stringWithFormat:@"Downloads Successful = %lu\nDownload Failed = %lu", - (unsigned long)downloadsSuccessful,(unsigned long)downloadsFailed] - notificationName:@"Downloads Finished" - iconData:nil - priority:0 - isSticky:NO - clickContext:nil]; - } - @catch (NSException *e) { - NSLog(@"ERROR: Growl notification failed (nextDownload - complete): %@: %@", [e name], [e description]); - [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (nextDownload - complete): %@: %@", [e name], [e description]]]; - } + @try + { + [GrowlApplicationBridge notifyWithTitle:@"Downloads Finished" + description:[NSString stringWithFormat:@"Downloads Successful = %lu\nDownload Failed = %lu", + (unsigned long)downloadsSuccessful,(unsigned long)downloadsFailed] + notificationName:@"Downloads Finished" + iconData:nil + priority:0 + isSticky:NO + clickContext:nil]; + } + @catch (NSException *e) { + NSLog(@"ERROR: Growl notification failed (nextDownload - complete): %@: %@", [e name], [e description]); + [self addToLog:[NSString stringWithFormat:@"ERROR: Growl notification failed (nextDownload - complete): %@: %@", [e name], [e description]]]; + } [[SUUpdater sharedUpdater] checkForUpdatesInBackground]; if (downloadsFailed>0) - [solutionsWindow makeKeyAndOrderFront:self]; + [solutionsWindow makeKeyAndOrderFront:self]; if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"AutoRetryFailed"] boolValue] && downloadsFailed>0) { NSDate *scheduledDate = [NSDate dateWithTimeIntervalSinceNow:60*[[[NSUserDefaults standardUserDefaults] valueForKey:@"AutoRetryTime"] doubleValue]]; @@ -2049,7 +2062,7 @@ - (IBAction)pvrSearch:(id)sender NSString *cacheExpiryArg = [self cacheExpiryArgument:nil]; NSString *typeArgument = [self typeArgument:nil]; NSArray *args = @[getiPlayerPath,noWarningArg,cacheExpiryArg,typeArgument,@"--nopurge", - @"--listformat=: , ~ - ~, , ",searchArgument,profileDirArg]; + @"--listformat=: , ~ - ~, , ",searchArgument,profileDirArg]; [pvrSearchTask setArguments:args]; [pvrSearchTask setStandardOutput:pvrSearchPipe]; @@ -2058,13 +2071,13 @@ - (IBAction)pvrSearch:(id)sender NSNotificationCenter *nc; nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self - selector:@selector(pvrSearchDataReady:) - name:NSFileHandleReadCompletionNotification - object:fh]; + selector:@selector(pvrSearchDataReady:) + name:NSFileHandleReadCompletionNotification + object:fh]; [nc addObserver:self - selector:@selector(pvrSearchFinished:) - name:NSTaskDidTerminateNotification - object:pvrSearchTask]; + selector:@selector(pvrSearchFinished:) + name:NSTaskDidTerminateNotification + object:pvrSearchTask]; pvrSearchData = [[NSMutableString alloc] init]; [pvrSearchTask launch]; [pvrSearchIndicator startAnimation:nil]; @@ -2074,12 +2087,12 @@ - (IBAction)pvrSearch:(id)sender - (void)pvrSearchDataReady:(NSNotification *)n { - NSData *d; - d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; + NSData *d; + d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; - if ([d length] > 0) { + if ([d length] > 0) { NSString *s = [[NSString alloc] initWithData:d - encoding:NSUTF8StringEncoding]; + encoding:NSUTF8StringEncoding]; [pvrSearchData appendString:s]; @@ -2089,9 +2102,9 @@ - (void)pvrSearchDataReady:(NSNotification *)n pvrSearchTask = nil; } - // If the task is running, start reading again - if (pvrSearchTask) - [[pvrSearchPipe fileHandleForReading] readInBackgroundAndNotify]; + // If the task is running, start reading again + if (pvrSearchTask) + [[pvrSearchPipe fileHandleForReading] readInBackgroundAndNotify]; } - (void)pvrSearchFinished:(NSNotification *)n { @@ -2103,7 +2116,7 @@ - (void)pvrSearchFinished:(NSNotification *)n NSRange currentRange; while (paraEnd < length) { [string getParagraphStart:¶Start end:¶End - contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; + contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); [array addObject:[string substringWithRange:currentRange]]; } @@ -2168,17 +2181,17 @@ - (IBAction)addToAutoRecord:(id)sender [show setValue:[programme timeadded] forKey:@"added"]; [show setValue:[programme tvNetwork] forKey:@"tvNetwork"]; [show setValue:[NSDate date] forKey:@"lastFound"]; - //Check to make sure the programme isn't already in the queue before adding it. - NSArray *queuedObjects = [pvrQueueController arrangedObjects]; - BOOL add=YES; - for (Programme *queuedShow in queuedObjects) - { - if ([[show showName] isEqualToString:[queuedShow showName]] && [show tvNetwork] == [queuedShow tvNetwork]) add=NO; - } - if (add) - { - [pvrQueueController addObject:show]; - } + //Check to make sure the programme isn't already in the queue before adding it. + NSArray *queuedObjects = [pvrQueueController arrangedObjects]; + BOOL add=YES; + for (Programme *queuedShow in queuedObjects) + { + if ([[show showName] isEqualToString:[queuedShow showName]] && [show tvNetwork] == [queuedShow tvNetwork]) add=NO; + } + if (add) + { + [pvrQueueController addObject:show]; + } } } - (IBAction)addSeriesLinkToQueue:(id)sender @@ -2213,18 +2226,18 @@ - (void)seriesLinkToQueueTimerSelector { if (!runDownloads) [currentProgress performSelectorOnMainThread:@selector(setStringValue:) withObject:[NSString stringWithFormat:@"Updating Series Link - %lu/%lu - %@",(unsigned long)[seriesLink indexOfObject:series]+1,(unsigned long)[seriesLink count],[series showName]] waitUntilDone:YES]; - if ([[series showName] length] == 0) { - [seriesToBeRemoved addObject:series]; - continue; - } else if ([[series tvNetwork] length] == 0) { - [series setTvNetwork:@"*"]; - } + if ([[series showName] length] == 0) { + [seriesToBeRemoved addObject:series]; + continue; + } else if ([[series tvNetwork] length] == 0) { + [series setTvNetwork:@"*"]; + } NSString *cacheExpiryArgument = [self cacheExpiryArgument:nil]; NSString *typeArgument = [self typeArgument:nil]; NSMutableArray *autoRecordArgs = [[NSMutableArray alloc] initWithObjects:getiPlayerPath, noWarningArg,@"--nopurge", - @"--listformat=: , ~ - ~, , , ,", cacheExpiryArgument, - typeArgument, profileDirArg,@"--hide",[self escapeSpecialCharactersInString:[series showName]],nil]; + @"--listformat=: , ~ - ~, , , ,", cacheExpiryArgument, + typeArgument, profileDirArg,@"--hide",[self escapeSpecialCharactersInString:[series showName]],nil]; NSTask *autoRecordTask = [[NSTask alloc] init]; NSPipe *autoRecordPipe = [[NSPipe alloc] init]; @@ -2259,14 +2272,14 @@ - (void)seriesLinkFinished:(NSNotification *)note [[NSNotificationCenter defaultCenter] removeObserver:self name:@"NSThreadWillExitNotification" object:nil]; //If this is an update initiated by the scheduler, run the downloads. - if (runScheduled && !scheduleTimer) + if (runScheduled && !scheduleTimer) { [self performSelectorOnMainThread:@selector(startDownloads:) withObject:self waitUntilDone:NO]; } [self performSelectorOnMainThread:@selector(scheduleTimerForFinished:) withObject:nil waitUntilDone:NO]; NSLog(@"Series-Link Thread Finished"); } - + - (void)scheduleTimerForFinished:(id)sender { [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(seriesLinkFinished2:) userInfo:currentProgress repeats:NO]; @@ -2293,7 +2306,7 @@ - (BOOL)processAutoRecordData:(NSString *)autoRecordData2 forSeries:(Series *)se NSRange currentRange; while (paraEnd < length) { [string getParagraphStart:¶Start end:¶End - contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; + contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); [array addObject:[string substringWithRange:currentRange]]; } @@ -2318,8 +2331,8 @@ - (BOOL)processAutoRecordData:(NSString *)autoRecordData2 forSeries:(Series *)se [myScanner scanUpToString:@", " intoString:nil]; [myScanner scanString:@", " intoString:nil]; [myScanner scanUpToString:@"," intoString:&temp_realPID]; - [myScanner scanString:@"," intoString:nil]; - [myScanner scanUpToString:@"kjkjkj" intoString:&url]; + [myScanner scanString:@"," intoString:nil]; + [myScanner scanUpToString:@"kjkjkj" intoString:&url]; NSScanner *seriesEpisodeScanner = [NSScanner scannerWithString:temp_showName]; NSString *series_Name, *episode_Name; @@ -2335,41 +2348,41 @@ - (BOOL)processAutoRecordData:(NSString *)autoRecordData2 forSeries:(Series *)se temp_showName = [temp_showName stringByAppendingFormat:@" - %@", temp_showName2]; } if (([[series2 added] integerValue] > timeadded) && - ([temp_tvNetwork isEqualToString:[series2 tvNetwork]] || [[[series2 tvNetwork] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@"*"] || [[series2 tvNetwork] length] == 0)) - { - [series2 setAdded:@(timeadded)]; - } + ([temp_tvNetwork isEqualToString:[series2 tvNetwork]] || [[[series2 tvNetwork] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@"*"] || [[series2 tvNetwork] length] == 0)) + { + [series2 setAdded:@(timeadded)]; + } if (([[series2 added] integerValue] <= timeadded) && - ([temp_tvNetwork isEqualToString:[series2 tvNetwork]] || [[[series2 tvNetwork] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@"*"] || [[series2 tvNetwork] length] == 0)) + ([temp_tvNetwork isEqualToString:[series2 tvNetwork]] || [[[series2 tvNetwork] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@"*"] || [[series2 tvNetwork] length] == 0)) { - @try { - oneFound=YES; - Programme *p = [[Programme alloc] initWithInfo:nil pid:temp_pid programmeName:temp_showName network:temp_tvNetwork]; - [p setRealPID:temp_realPID]; - [p setSeriesName:series_Name]; - [p setEpisodeName:episode_Name]; - [p setUrl:url]; - if ([temp_type isEqualToString:@"radio"]) [p setValue:@YES forKey:@"radio"]; - else if ([temp_type isEqualToString:@"podcast"]) [p setPodcast:@YES]; - [p setValue:@"Added by Series-Link" forKey:@"status"]; - BOOL inQueue=NO; - for (Programme *show in currentQueue) - if ([[show showName] isEqualToString:[p showName]] && [[show pid] isEqualToString:[p pid]]) inQueue=YES; - if (!inQueue) - { - if (runDownloads) [p setValue:@"Waiting..." forKey:@"status"]; - [queueController performSelectorOnMainThread:@selector(addObject:) withObject:p waitUntilDone:NO]; - } - } - @catch (NSException *e) { - NSAlert *queueException = [[NSAlert alloc] init]; - [queueException addButtonWithTitle:@"OK"]; - [queueException setMessageText:[NSString stringWithFormat:@"Series-Link to Queue Transfer Failed"]]; - [queueException setInformativeText:@"The recording queue is in an unknown state. Please restart GiA and clear the recording queue."]; - [queueException setAlertStyle:NSWarningAlertStyle]; - [queueException runModal]; - queueException = nil; - } + @try { + oneFound=YES; + Programme *p = [[Programme alloc] initWithInfo:nil pid:temp_pid programmeName:temp_showName network:temp_tvNetwork]; + [p setRealPID:temp_realPID]; + [p setSeriesName:series_Name]; + [p setEpisodeName:episode_Name]; + [p setUrl:url]; + if ([temp_type isEqualToString:@"radio"]) [p setValue:@YES forKey:@"radio"]; + else if ([temp_type isEqualToString:@"podcast"]) [p setPodcast:@YES]; + [p setValue:@"Added by Series-Link" forKey:@"status"]; + BOOL inQueue=NO; + for (Programme *show in currentQueue) + if ([[show showName] isEqualToString:[p showName]] && [[show pid] isEqualToString:[p pid]]) inQueue=YES; + if (!inQueue) + { + if (runDownloads) [p setValue:@"Waiting..." forKey:@"status"]; + [queueController performSelectorOnMainThread:@selector(addObject:) withObject:p waitUntilDone:NO]; + } + } + @catch (NSException *e) { + NSAlert *queueException = [[NSAlert alloc] init]; + [queueException addButtonWithTitle:@"OK"]; + [queueException setMessageText:[NSString stringWithFormat:@"Series-Link to Queue Transfer Failed"]]; + [queueException setInformativeText:@"The recording queue is in an unknown state. Please restart GiA and clear the recording queue."]; + [queueException setAlertStyle:NSWarningAlertStyle]; + [queueException runModal]; + queueException = nil; + } } } @catch (NSException *e) { @@ -2408,27 +2421,27 @@ - (BOOL)processAutoRecordData:(NSString *)autoRecordData2 forSeries:(Series *)se #pragma mark Misc. - (void)saveAppData { - //Save Queue & Series-Link + //Save Queue & Series-Link NSMutableArray *tempQueue = [[NSMutableArray alloc] initWithArray:[queueController arrangedObjects]]; NSMutableArray *tempSeries = [[NSMutableArray alloc] initWithArray:[pvrQueueController arrangedObjects]]; NSMutableArray *temptempQueue = [[NSMutableArray alloc] initWithArray:tempQueue]; for (Programme *show in temptempQueue) { if (([[show complete] isEqualToNumber:@YES] && [[show successful] isEqualToNumber:@YES]) - || [[show status] isEqualToString:@"Added by Series-Link"]) [tempQueue removeObject:show]; + || [[show status] isEqualToString:@"Added by Series-Link"]) [tempQueue removeObject:show]; } NSMutableArray *temptempSeries = [[NSMutableArray alloc] initWithArray:tempSeries]; for (Series *series in temptempSeries) { - if ([[series showName] length] == 0) { - [tempSeries removeObject:series]; - } else if ([[series tvNetwork] length] == 0) { - [series setTvNetwork:@"*"]; - } - + if ([[series showName] length] == 0) { + [tempSeries removeObject:series]; + } else if ([[series tvNetwork] length] == 0) { + [series setTvNetwork:@"*"]; + } + } NSFileManager *fileManager = [NSFileManager defaultManager]; - + NSString *folder = @"~/Library/Application Support/Get iPlayer Automator/"; folder = [folder stringByExpandingTildeInPath]; if ([fileManager fileExistsAtPath: folder] == NO) @@ -2440,10 +2453,10 @@ - (void)saveAppData NSMutableDictionary * rootObject; rootObject = [NSMutableDictionary dictionary]; - + [rootObject setValue:tempQueue forKey:@"queue"]; [rootObject setValue:tempSeries forKey:@"serieslink"]; - [rootObject setValue:lastUpdate forKey:@"lastUpdate"]; + [rootObject setValue:lastUpdate forKey:@"lastUpdate"]; [NSKeyedArchiver archiveRootObject: rootObject toFile: filePath]; filename = @"Formats.automatorqueue"; @@ -2453,48 +2466,48 @@ - (void)saveAppData [rootObject setValue:[tvFormatController arrangedObjects] forKey:@"tvFormats"]; [rootObject setValue:[radioFormatController arrangedObjects] forKey:@"radioFormats"]; - [rootObject setValue:@YES forKey:@"hasUpdatedCacheFor4oD"]; + [rootObject setValue:@YES forKey:@"hasUpdatedCacheFor4oD"]; [NSKeyedArchiver archiveRootObject:rootObject toFile:filePath]; - - filename = @"ITVFormats.automator"; - filePath = [folder stringByAppendingPathComponent:filename]; - rootObject = [NSMutableDictionary dictionary]; - [rootObject setValue:[itvFormatController arrangedObjects] forKey:@"itvFormats"]; - [NSKeyedArchiver archiveRootObject:rootObject toFile:filePath]; - - //Store Preferences in case of crash - [[NSUserDefaults standardUserDefaults] synchronize]; + + filename = @"ITVFormats.automator"; + filePath = [folder stringByAppendingPathComponent:filename]; + rootObject = [NSMutableDictionary dictionary]; + [rootObject setValue:[itvFormatController arrangedObjects] forKey:@"itvFormats"]; + [NSKeyedArchiver archiveRootObject:rootObject toFile:filePath]; + + //Store Preferences in case of crash + [[NSUserDefaults standardUserDefaults] synchronize]; } - (IBAction)closeWindow:(id)sender { - if ([logWindow isKeyWindow]) [logWindow performClose:self]; - else if ([historyWindow isKeyWindow]) [historyWindow performClose:self]; - else if ([pvrPanel isKeyWindow]) [pvrPanel performClose:self]; - else if ([prefsPanel isKeyWindow]) [prefsPanel performClose:self]; - else if ([mainWindow isKeyWindow]) - { - NSAlert *downloadAlert = [NSAlert alertWithMessageText:@"Are you sure you wish to quit?" - defaultButton:@"Yes" - alternateButton:@"No" - otherButton:nil - informativeTextWithFormat:nil]; - NSInteger response = [downloadAlert runModal]; - if (response == NSAlertDefaultReturn) [mainWindow performClose:self]; - } + if ([logWindow isKeyWindow]) [logWindow performClose:self]; + else if ([historyWindow isKeyWindow]) [historyWindow performClose:self]; + else if ([pvrPanel isKeyWindow]) [pvrPanel performClose:self]; + else if ([prefsPanel isKeyWindow]) [prefsPanel performClose:self]; + else if ([mainWindow isKeyWindow]) + { + NSAlert *downloadAlert = [NSAlert alertWithMessageText:@"Are you sure you wish to quit?" + defaultButton:@"Yes" + alternateButton:@"No" + otherButton:nil + informativeTextWithFormat:nil]; + NSInteger response = [downloadAlert runModal]; + if (response == NSAlertDefaultReturn) [mainWindow performClose:self]; + } } - (NSString *)escapeSpecialCharactersInString:(NSString *)string { - NSArray *characters = @[@"+",@"-",@"&",@"!",@"(",@")",@"{",@"}", + NSArray *characters = @[@"+",@"-",@"&",@"!",@"(",@")",@"{",@"}", @"[",@"]"@"^",@"~",@"*",@"?",@":",@"\""]; - for (NSString *character in characters) - string = [string stringByReplacingOccurrencesOfString:character withString:[NSString stringWithFormat:@"\\%@",character]]; - - return string; + for (NSString *character in characters) + string = [string stringByReplacingOccurrencesOfString:character withString:[NSString stringWithFormat:@"\\%@",character]]; + + return string; } - (void)thirtyTwoBitModeAlert { - if ([[NSAlert alertWithMessageText:@"File could not be added to iTunes," defaultButton:@"Help Me!" alternateButton:@"Do nothing" otherButton:nil informativeTextWithFormat:@"This is usually fixed by running iTunes in 32-bit mode. Would you like instructions to do this?"] runModal] == NSAlertDefaultReturn) - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://support.apple.com/kb/TS3771"]]; + if ([[NSAlert alertWithMessageText:@"File could not be added to iTunes," defaultButton:@"Help Me!" alternateButton:@"Do nothing" otherButton:nil informativeTextWithFormat:@"This is usually fixed by running iTunes in 32-bit mode. Would you like instructions to do this?"] runModal] == NSAlertDefaultReturn) + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://support.apple.com/kb/TS3771"]]; } - (void)addToiTunes:(Programme *)show { @@ -2512,7 +2525,7 @@ - (void)addToiTunes:(Programme *)show if ([ext isEqualToString:@"mov"] || [ext isEqualToString:@"mp4"] || [ext isEqualToString:@"mp3"] || [ext isEqualToString:@"m4a"]) { iTunesTrack *track = [iTunes add:fileToAdd to:nil]; - NSLog(@"Track exists = %@", ([track exists] ? @"YES" : @"NO")); + NSLog(@"Track exists = %@", ([track exists] ? @"YES" : @"NO")); if ([track exists] && ([ext isEqualToString:@"mov"] || [ext isEqualToString:@"mp4"])) { if ([ext isEqualToString:@"mov"]) @@ -2525,21 +2538,21 @@ - (void)addToiTunes:(Programme *)show if ([show episode]>0) [track setEpisodeNumber:[show episode]]; } [track setUnplayed:YES]; - [show setValue:@"Complete & in iTunes" forKey:@"status"]; + [show setValue:@"Complete & in iTunes" forKey:@"status"]; } else if ([track exists] && ([ext isEqualToString:@"mp3"] || [ext isEqualToString:@"m4a"])) { [track setBookmarkable:YES]; [track setUnplayed:YES]; - [show setValue:@"Complete & in iTunes" forKey:@"status"]; + [show setValue:@"Complete & in iTunes" forKey:@"status"]; } else - { - [self performSelectorOnMainThread:@selector(addToLog:) withObject:@"iTunes did not accept file." waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(addToLog:) withObject:@"Try setting iTunes to open in 32-bit mode." waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(thirtyTwoBitModeAlert) withObject:nil waitUntilDone:NO]; - [show setValue:@"Complete: Not in iTunes" forKey:@"status"]; - } + { + [self performSelectorOnMainThread:@selector(addToLog:) withObject:@"iTunes did not accept file." waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(addToLog:) withObject:@"Try setting iTunes to open in 32-bit mode." waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(thirtyTwoBitModeAlert) withObject:nil waitUntilDone:NO]; + [show setValue:@"Complete: Not in iTunes" forKey:@"status"]; + } } else { @@ -2551,20 +2564,20 @@ - (void)addToiTunes:(Programme *)show @catch (NSException *e) { [self performSelectorOnMainThread:@selector(addToLog:) withObject:@"Unable to Add to iTunes" waitUntilDone:YES]; - NSLog(@"Unable %@ to iTunes",show); + NSLog(@"Unable %@ to iTunes",show); [show setValue:@"Complete, Could not add to iTunes." forKey:@"status"]; } } - (void)cleanUpPath:(Programme *)show { - + //Process Show Name into Parts NSString *originalShowName, *originalEpisodeName; NSScanner *nameScanner = [NSScanner scannerWithString:[show showName]]; [nameScanner scanUpToString:@" - " intoString:&originalShowName]; [nameScanner scanString:@"-" intoString:nil]; [nameScanner scanUpToString:@"Scan to End" intoString:&originalEpisodeName]; - + //Replace :'s with -'s NSString *showName = [originalShowName stringByReplacingOccurrencesOfString:@":" withString:@" -"]; @@ -2626,7 +2639,7 @@ - (IBAction)chooseDownloadPath:(id)sender [openPanel setCanChooseFiles:NO]; [openPanel setCanChooseDirectories:YES]; [openPanel setAllowsMultipleSelection:NO]; - [openPanel setCanCreateDirectories:YES]; + [openPanel setCanCreateDirectories:YES]; [openPanel runModal]; NSArray *urls = [openPanel URLs]; [[NSUserDefaults standardUserDefaults] setValue:[urls[0] path] forKey:@"DownloadPath"]; @@ -2636,7 +2649,7 @@ - (IBAction)showFeedback:(id)sender [JRFeedbackController showFeedback]; } - (IBAction)restoreDefaults:(id)sender -{ +{ NSUserDefaults *sharedDefaults = [NSUserDefaults standardUserDefaults]; [sharedDefaults removeObjectForKey:@"DownloadPath"]; [sharedDefaults removeObjectForKey:@"Proxy"]; @@ -2651,7 +2664,7 @@ - (IBAction)restoreDefaults:(id)sender [sharedDefaults removeObjectForKey:@"CacheITV_TV"]; [sharedDefaults removeObjectForKey:@"CacheBBC_Radio"]; [sharedDefaults removeObjectForKey:@"CacheBBC_Podcasts"]; - [sharedDefaults removeObjectForKey:@"Cache4oD_TV"]; + [sharedDefaults removeObjectForKey:@"Cache4oD_TV"]; [sharedDefaults removeObjectForKey:@"CacheExpiryTime"]; [sharedDefaults removeObjectForKey:@"Verbose"]; [sharedDefaults removeObjectForKey:@"SeriesLinkStartup"]; @@ -2661,8 +2674,13 @@ - (IBAction)restoreDefaults:(id)sender } - (void)applescriptStartDownloads { - runScheduled=YES; - [self forceUpdate:self]; + runScheduled=YES; + [self forceUpdate:self]; +} + ++ (AppController *)sharedController +{ + return sharedController; } #pragma mark Argument Retrieval - (NSString *)typeArgument:(id)sender @@ -2671,15 +2689,15 @@ - (NSString *)typeArgument:(id)sender { NSMutableString *typeArgument = [[NSMutableString alloc] initWithString:@"--type="]; if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"CacheBBC_TV"] isEqualTo:@YES]) - [typeArgument appendString:@"tv,"]; + [typeArgument appendString:@"tv,"]; if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"CacheITV_TV"] isEqualTo:@YES]) - [typeArgument appendString:@"itv,"]; + [typeArgument appendString:@"itv,"]; if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"CacheBBC_Radio"] isEqualTo:@YES]) - [typeArgument appendString:@"radio,"]; + [typeArgument appendString:@"radio,"]; if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"CacheBBC_Podcasts"] isEqualTo:@YES]) - [typeArgument appendString:@"podcast,"]; - if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"Cache4oD_TV"] isEqualTo:@YES]) - [typeArgument appendString:@"ch4,"]; + [typeArgument appendString:@"podcast,"]; + if ([[[NSUserDefaults standardUserDefaults] valueForKey:@"Cache4oD_TV"] isEqualTo:@YES]) + [typeArgument appendString:@"ch4,"]; [typeArgument deleteCharactersInRange:NSMakeRange([typeArgument length]-1,1)]; currentTypeArgument = [typeArgument copy]; return [NSString stringWithString:typeArgument]; @@ -2709,11 +2727,11 @@ - (IBAction)showScheduleWindow:(id)sender } else { - NSAlert *alert = [NSAlert alertWithMessageText:@"Downloads are already running." - defaultButton:@"OK" - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"You cannot schedule downloads to start if they are already running."]; + NSAlert *alert = [NSAlert alertWithMessageText:@"Downloads are already running." + defaultButton:@"OK" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"You cannot schedule downloads to start if they are already running."]; [alert runModal]; } } @@ -2724,17 +2742,17 @@ - (IBAction)cancelSchedule:(id)sender - (IBAction)scheduleStart:(id)sender { NSDate *startTime = [datePicker dateValue]; - scheduleTimer = [[NSTimer alloc] initWithFireDate:startTime - interval:1 - target:self - selector:@selector(runScheduledDownloads:) - userInfo:nil - repeats:NO]; + scheduleTimer = [[NSTimer alloc] initWithFireDate:startTime + interval:1 + target:self + selector:@selector(runScheduledDownloads:) + userInfo:nil + repeats:NO]; interfaceTimer = [NSTimer scheduledTimerWithTimeInterval:1 - target:self - selector:@selector(updateScheduleStatus:) - userInfo:nil - repeats:YES]; + target:self + selector:@selector(updateScheduleStatus:) + userInfo:nil + repeats:YES]; if ([scheduleWindow isVisible]) [scheduleWindow close]; [startButton setEnabled:NO]; @@ -2766,8 +2784,8 @@ - (void)updateScheduleStatus:(NSTimer *)theTimer NSDateComponents *conversionInfo = [[NSCalendar currentCalendar] components:unitFlags fromDate:currentTime toDate:startTime options:0]; NSString *status = [NSString stringWithFormat:@"Time until Start (DD:HH:MM:SS): %02ld:%02ld:%02ld:%02ld", - (long)[conversionInfo day], (long)[conversionInfo hour], - (long)[conversionInfo minute],(long)[conversionInfo second]]; + (long)[conversionInfo day], (long)[conversionInfo hour], + (long)[conversionInfo minute],(long)[conversionInfo second]]; if (!runUpdate) [currentProgress setStringValue:status]; [currentIndicator setIndeterminate:YES]; @@ -2797,11 +2815,11 @@ - (IBAction)showLiveTVWindow:(id)sender } else { - NSAlert *downloadRunning = [NSAlert alertWithMessageText:@"Downloads are Running!" - defaultButton:@"Continue" - alternateButton:@"Cancel" - otherButton:nil - informativeTextWithFormat:@"You may experience choppy playback while downloads are running."]; + NSAlert *downloadRunning = [NSAlert alertWithMessageText:@"Downloads are Running!" + defaultButton:@"Continue" + alternateButton:@"Cancel" + otherButton:nil + informativeTextWithFormat:@"You may experience choppy playback while downloads are running."]; NSInteger response = [downloadRunning runModal]; if (response == NSAlertDefaultReturn) { @@ -2812,13 +2830,13 @@ - (IBAction)showLiveTVWindow:(id)sender - (IBAction)startLiveTV:(id)sender { - [self loadProxyInBackgroundForSelector:@selector(startLiveTV:proxyError:) withObject:sender]; + [self loadProxyInBackgroundForSelector:@selector(startLiveTV:proxyError:) withObject:sender]; } - (IBAction)startLiveTV:(id)sender proxyError:(NSError *)proxyError { - if ([proxyError code] == kProxyLoadCancelled) - return; + if ([proxyError code] == kProxyLoadCancelled) + return; getiPlayerStreamer = [[NSTask alloc] init]; mplayerStreamer = [[NSTask alloc] init]; liveTVPipe = [[NSPipe alloc] init]; @@ -2838,26 +2856,26 @@ - (IBAction)startLiveTV:(id)sender proxyError:(NSError *)proxyError //Set Proxy Arguments NSString *proxyArg = NULL; NSString *partialProxyArg = NULL; - if (proxy) - { - proxyArg = [[NSString alloc] initWithFormat:@"-p%@", [proxy url]]; - if (![[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) - { - partialProxyArg = @"--partial-proxy"; - } - } - - //Prepare Arguments + if (proxy) + { + proxyArg = [[NSString alloc] initWithFormat:@"-p%@", [proxy url]]; + if (![[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) + { + partialProxyArg = @"--partial-proxy"; + } + } + + //Prepare Arguments NSArray *args = @[[[NSBundle mainBundle] pathForResource:@"get_iplayer" ofType:@"pl"], - profileDirArg, - @"--stream", - @"--modes=flashnormal", - @"--type=livetv", - [selectedChannel channel], - //@"--player=mplayer -cache 3072 -", - // [NSString stringWithFormat:@"--player=\"%@\" -cache 3072 -", [[NSBundle mainBundle] pathForResource:@"mplayer" ofType:nil]], - proxyArg, - partialProxyArg]; + profileDirArg, + @"--stream", + @"--modes=flashnormal", + @"--type=livetv", + [selectedChannel channel], + //@"--player=mplayer -cache 3072 -", + // [NSString stringWithFormat:@"--player=\"%@\" -cache 3072 -", [[NSBundle mainBundle] pathForResource:@"mplayer" ofType:nil]], + proxyArg, + partialProxyArg]; [getiPlayerStreamer setArguments:args]; [mplayerStreamer setArguments:@[@"-cache",@"3072",@"-"]]; @@ -2880,280 +2898,378 @@ - (IBAction)stopLiveTV:(id)sender #pragma mark Proxy - (void)loadProxyInBackgroundForSelector:(SEL)selector withObject:(id)object { - [self updateProxyLoadStatus:YES message:@"Loading proxy settings..."]; - NSLog(@"INFO: Loading proxy settings..."); - [self addToLog:@"\n\nINFO: Loading proxy settings..."]; - [proxyDict removeAllObjects]; - proxyDict[@"selector"] = [NSValue valueWithPointer:selector]; - if (object) - proxyDict[@"object"] = object; - proxy = nil; - NSString *proxyOption = [[NSUserDefaults standardUserDefaults] valueForKey:@"Proxy"]; + [self loadProxyInBackgroundForSelector:selector withObject:object onTarget:self]; +} + +- (void)loadProxyInBackgroundForSelector:(SEL)selector withObject:(id)object onTarget:(id)target +{ + [self updateProxyLoadStatus:YES message:@"Loading proxy settings..."]; + NSLog(@"INFO: Loading proxy settings..."); + [self addToLog:@"\n\nINFO: Loading proxy settings..."]; + [proxyDict removeAllObjects]; + proxyDict[@"selector"] = [NSValue valueWithPointer:selector]; + proxyDict[@"target"] = target; + if (object) + proxyDict[@"object"] = object; + proxy = nil; + NSString *proxyOption = [[NSUserDefaults standardUserDefaults] valueForKey:@"Proxy"]; if ([proxyOption isEqualToString:@"Custom"]) { - NSString *customProxy = [[NSUserDefaults standardUserDefaults] valueForKey:@"CustomProxy"]; - NSLog(@"INFO: Custom Proxy: address=[%@] length=%ld", customProxy, [customProxy length]); - [self addToLog:[NSString stringWithFormat:@"INFO: Custom Proxy: address=[%@] length=%ld", customProxy, [customProxy length]]]; - NSString *proxyValue = [[customProxy lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if ([proxyValue length] == 0) - { - NSLog(@"WARNING: Custom proxy setting was blank. No proxy will be used."); - [self addToLog:@"WARNING: Custom proxy setting was blank. No proxy will be used."]; - if (!runScheduled) + NSString *customProxy = [[NSUserDefaults standardUserDefaults] valueForKey:@"CustomProxy"]; + NSLog(@"INFO: Custom Proxy: address=[%@] length=%ld", customProxy, [customProxy length]); + [self addToLog:[NSString stringWithFormat:@"INFO: Custom Proxy: address=[%@] length=%ld", customProxy, [customProxy length]]]; + NSString *proxyValue = [[customProxy lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([proxyValue length] == 0) + { + NSLog(@"WARNING: Custom proxy setting was blank. No proxy will be used."); + [self addToLog:@"WARNING: Custom proxy setting was blank. No proxy will be used."]; + if (!runScheduled) + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Custom proxy setting was blank.\nDownloads may fail.\nDo you wish to continue?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@""]; + [alert setAlertStyle:NSCriticalAlertStyle]; + if ([alert runModal] == NSAlertDefaultReturn) { - NSAlert *alert = [NSAlert alertWithMessageText:@"Custom proxy setting was blank.\nDownloads may fail.\nDo you wish to continue?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@""]; - [alert setAlertStyle:NSCriticalAlertStyle]; - if ([alert runModal] == NSAlertDefaultReturn) - { - [self cancelProxyLoad]; - } - else - { - [self failProxyLoad]; - } + [self cancelProxyLoad]; + } + else + { + [self failProxyLoad]; } - } - else - { - proxy = [[HTTPProxy alloc] initWithString:proxyValue]; - [self finishProxyLoad]; - } + } + } + else + { + proxy = [[HTTPProxy alloc] initWithString:proxyValue]; + [self finishProxyLoad]; + } } else if ([proxyOption isEqualToString:@"Provided"]) { - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://tom-tech.com/get_iplayer/proxy.txt"]]; - [request setUserInfo:@{@"selector": [NSValue valueWithPointer:selector], @"object": object}]; - [request setDelegate:self]; - [request setDidFailSelector:@selector(providedProxyDidFinish:)]; - [request setDidFinishSelector:@selector(providedProxyDidFinish:)]; - [request setTimeOutSeconds:10]; - [request setNumberOfTimesToRetryOnTimeout:2]; - [self updateProxyLoadStatus:YES message:[NSString stringWithFormat:@"Loading provided proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]]]; - NSLog(@"INFO: Loading provided proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]); - [self addToLog:[NSString stringWithFormat:@"INFO: Loading provided proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]*2]]; - [request startAsynchronous]; - } - else - { - NSLog(@"INFO: No proxy to load"); - [self addToLog:@"INFO: No proxy to load"]; - [self finishProxyLoad]; - } + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://tom-tech.com/get_iplayer/proxy.txt"]]; + [request setUserInfo:@{@"selector": [NSValue valueWithPointer:selector], @"object": object}]; + [request setDelegate:self]; + [request setDidFailSelector:@selector(providedProxyDidFinish:)]; + [request setDidFinishSelector:@selector(providedProxyDidFinish:)]; + [request setTimeOutSeconds:10]; + [request setNumberOfTimesToRetryOnTimeout:2]; + [self updateProxyLoadStatus:YES message:[NSString stringWithFormat:@"Loading provided proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]]]; + NSLog(@"INFO: Loading provided proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]); + [self addToLog:[NSString stringWithFormat:@"INFO: Loading provided proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]*2]]; + [request startAsynchronous]; + } + else + { + NSLog(@"INFO: No proxy to load"); + [self addToLog:@"INFO: No proxy to load"]; + [self finishProxyLoad]; + } } - (void)providedProxyDidFinish:(ASIHTTPRequest *)request { - NSData *urlData = [request responseData]; - if ([request responseStatusCode] != 200 || !urlData) - { - NSLog(@"WARNING: Provided proxy could not be retrieved. No proxy will be used."); - [self addToLog:@"WARNING: Provided proxy could not be retrieved. No proxy will be used."]; - if (!runScheduled) - { - NSError *error = [request error]; - NSAlert *alert = [NSAlert alertWithMessageText:@"Provided proxy could not be retrieved.\nDownloads may fail.\nDo you wish to continue?" + NSData *urlData = [request responseData]; + if ([request responseStatusCode] != 200 || !urlData) + { + NSLog(@"WARNING: Provided proxy could not be retrieved. No proxy will be used."); + [self addToLog:@"WARNING: Provided proxy could not be retrieved. No proxy will be used."]; + if (!runScheduled) + { + NSError *error = [request error]; + NSAlert *alert = [NSAlert alertWithMessageText:@"Provided proxy could not be retrieved.\nDownloads may fail.\nDo you wish to continue?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@"Error: %@", (error ? [error localizedDescription] : @"Unknown error")]; + [alert setAlertStyle:NSCriticalAlertStyle]; + if ([alert runModal] == NSAlertDefaultReturn) + [self cancelProxyLoad]; + else + [self failProxyLoad]; + } + } + else + { + NSString *proxyValue = [[[[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding] lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([proxyValue length] == 0) + { + NSLog(@"WARNING: Provided proxy value was blank. No proxy will be used."); + [self addToLog:@"WARNING: Provided proxy value was blank. No proxy will be used."]; + if (!runScheduled) + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Provided proxy value was blank.\nDownloads may fail.\nDo you wish to continue?" defaultButton:@"No" alternateButton:@"Yes" otherButton:nil - informativeTextWithFormat:@"Error: %@", (error ? [error localizedDescription] : @"Unknown error")]; + informativeTextWithFormat:@""]; [alert setAlertStyle:NSCriticalAlertStyle]; if ([alert runModal] == NSAlertDefaultReturn) - [self cancelProxyLoad]; + [self cancelProxyLoad]; else - [self failProxyLoad]; - } - } - else - { - NSString *proxyValue = [[[[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding] lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if ([proxyValue length] == 0) - { - NSLog(@"WARNING: Provided proxy value was blank. No proxy will be used."); - [self addToLog:@"WARNING: Provided proxy value was blank. No proxy will be used."]; - if (!runScheduled) - { - NSAlert *alert = [NSAlert alertWithMessageText:@"Provided proxy value was blank.\nDownloads may fail.\nDo you wish to continue?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@""]; - [alert setAlertStyle:NSCriticalAlertStyle]; - if ([alert runModal] == NSAlertDefaultReturn) - [self cancelProxyLoad]; - else - [self failProxyLoad]; - } - } - else - { - proxy = [[HTTPProxy alloc] initWithString:proxyValue]; - [self finishProxyLoad]; - } - } + [self failProxyLoad]; + } + } + else + { + proxy = [[HTTPProxy alloc] initWithString:proxyValue]; + [self finishProxyLoad]; + } + } } - (void)cancelProxyLoad { - [self returnFromProxyLoadWithError:[NSError errorWithDomain:@"Proxy" code:kProxyLoadCancelled userInfo:@{NSLocalizedDescriptionKey: @"Proxy Load Cancelled"}]]; + [self returnFromProxyLoadWithError:[NSError errorWithDomain:@"Proxy" code:kProxyLoadCancelled userInfo:@{NSLocalizedDescriptionKey: @"Proxy Load Cancelled"}]]; } - (void)failProxyLoad { - [self returnFromProxyLoadWithError:[NSError errorWithDomain:@"Proxy" code:kProxyLoadFailed userInfo:@{NSLocalizedDescriptionKey: @"Proxy Load Failed"}]]; + [self returnFromProxyLoadWithError:[NSError errorWithDomain:@"Proxy" code:kProxyLoadFailed userInfo:@{NSLocalizedDescriptionKey: @"Proxy Load Failed"}]]; } - (void)finishProxyLoad { - NSLog(@"INFO: Proxy load complete."); - [self addToLog:@"INFO: Proxy load complete."]; - if (proxy && [[NSUserDefaults standardUserDefaults] boolForKey:@"TestProxy"]) - { - [self testProxyOnLoad]; - return; - } - [self returnFromProxyLoadWithError:nil]; + NSLog(@"INFO: Proxy load complete."); + [self addToLog:@"INFO: Proxy load complete."]; + if (proxy && [[NSUserDefaults standardUserDefaults] boolForKey:@"TestProxy"]) + { + [self testProxyOnLoad]; + return; + } + [self returnFromProxyLoadWithError:nil]; } - (void)testProxyOnLoad { - if (proxy) - { - if (!proxy.host || [proxy.host length] == 0 || [proxy.host rangeOfString:@"(null)"].location != NSNotFound) - { - NSLog(@"WARNING: Invalid proxy host: address=%@ length=%ld", proxy.host, [proxy.host length]); - [self addToLog:[NSString stringWithFormat:@"WARNING: Invalid proxy host: address=%@ length=%ld", proxy.host, [proxy.host length]]]; - if (!runScheduled) - { - NSAlert *alert = [NSAlert alertWithMessageText:@"Invalid proxy host.\nDownloads may fail.\nDo you wish to continue?" - defaultButton:@"No" - alternateButton:@"Yes" - otherButton:nil - informativeTextWithFormat:@"Invalid proxy host: address=[%@] length=%ld", proxy.host, [proxy.host length]]; - [alert setAlertStyle:NSCriticalAlertStyle]; - if ([alert runModal] == NSAlertDefaultReturn) - [self cancelProxyLoad]; - else - [self failProxyTest]; - } - return; - } - NSString *testURL = [[NSUserDefaults standardUserDefaults] stringForKey:@"ProxyTestURL"]; - if (!testURL) - testURL = @"http://www.google.com"; - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:testURL]]; - [request setDelegate:self]; - [request setDidFailSelector:@selector(proxyTestDidFinish:)]; - [request setDidFinishSelector:@selector(proxyTestDidFinish:)]; - [request setTimeOutSeconds:30]; - [request setProxyType:proxy.type]; - [request setProxyHost:proxy.host]; - if (proxy.port) { - [request setProxyPort:proxy.port]; - } else { - if ([proxy.type isEqualToString:(NSString *)kCFProxyTypeHTTPS]) { - [request setProxyPort:443]; - } else { - [request setProxyPort:80]; - } - } - if (proxy.user) { - [request setProxyUsername:proxy.user]; - [request setProxyPassword:proxy.password]; - } - [self updateProxyLoadStatus:YES message:[NSString stringWithFormat:@"Testing proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]]]; - NSLog(@"INFO: Testing proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]); - [self addToLog:[NSString stringWithFormat:@"INFO: Testing proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]]]; - [request startAsynchronous]; - } - else - { - NSLog(@"INFO: No proxy to test"); - [self addToLog:@"INFO: No proxy to test"]; - [self finishProxyTest]; - } -} - -- (void)proxyTestDidFinish:(ASIHTTPRequest *)request -{ - if ([request responseStatusCode] != 200) - { - NSLog(@"WARNING: Proxy failed to load test page: %@", [request url]); - [self addToLog:[NSString stringWithFormat:@"WARNING: Proxy failed to load test page: %@", [request url]]]; - if (!runScheduled) - { - NSError *error = [request error]; - NSAlert *alert = [NSAlert alertWithMessageText:@"Proxy failed to load test page.\nDownloads may fail.\nDo you wish to continue?" + if (proxy) + { + if (!proxy.host || [proxy.host length] == 0 || [proxy.host rangeOfString:@"(null)"].location != NSNotFound) + { + NSLog(@"WARNING: Invalid proxy host: address=%@ length=%ld", proxy.host, [proxy.host length]); + [self addToLog:[NSString stringWithFormat:@"WARNING: Invalid proxy host: address=%@ length=%ld", proxy.host, [proxy.host length]]]; + if (!runScheduled) + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Invalid proxy host.\nDownloads may fail.\nDo you wish to continue?" defaultButton:@"No" alternateButton:@"Yes" otherButton:nil - informativeTextWithFormat:@"Failed to load %@ within %ld seconds\nUsing proxy: %@\nError: %@", [request url], (NSInteger)[request timeOutSeconds], [proxy url], (error ? [error localizedDescription] : @"Unknown error")]; + informativeTextWithFormat:@"Invalid proxy host: address=[%@] length=%ld", proxy.host, [proxy.host length]]; [alert setAlertStyle:NSCriticalAlertStyle]; if ([alert runModal] == NSAlertDefaultReturn) - [self cancelProxyLoad]; + [self cancelProxyLoad]; else - [self failProxyTest]; - } - } - else - { - [self finishProxyTest]; - } + [self failProxyTest]; + } + return; + } + NSString *testURL = [[NSUserDefaults standardUserDefaults] stringForKey:@"ProxyTestURL"]; + if (!testURL) + testURL = @"http://www.google.com"; + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:testURL]]; + [request setDelegate:self]; + [request setDidFailSelector:@selector(proxyTestDidFinish:)]; + [request setDidFinishSelector:@selector(proxyTestDidFinish:)]; + [request setTimeOutSeconds:30]; + [request setProxyType:proxy.type]; + [request setProxyHost:proxy.host]; + if (proxy.port) { + [request setProxyPort:proxy.port]; + } else { + if ([proxy.type isEqualToString:(NSString *)kCFProxyTypeHTTPS]) { + [request setProxyPort:443]; + } else { + [request setProxyPort:80]; + } + } + if (proxy.user) { + [request setProxyUsername:proxy.user]; + [request setProxyPassword:proxy.password]; + } + [self updateProxyLoadStatus:YES message:[NSString stringWithFormat:@"Testing proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]]]; + NSLog(@"INFO: Testing proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]); + [self addToLog:[NSString stringWithFormat:@"INFO: Testing proxy (may take up to %ld seconds)...", (NSInteger)[request timeOutSeconds]]]; + [request startAsynchronous]; + } + else + { + NSLog(@"INFO: No proxy to test"); + [self addToLog:@"INFO: No proxy to test"]; + [self finishProxyTest]; + } +} + +- (void)proxyTestDidFinish:(ASIHTTPRequest *)request +{ + if ([request responseStatusCode] != 200) + { + NSLog(@"WARNING: Proxy failed to load test page: %@", [request url]); + [self addToLog:[NSString stringWithFormat:@"WARNING: Proxy failed to load test page: %@", [request url]]]; + if (!runScheduled) + { + NSError *error = [request error]; + NSAlert *alert = [NSAlert alertWithMessageText:@"Proxy failed to load test page.\nDownloads may fail.\nDo you wish to continue?" + defaultButton:@"No" + alternateButton:@"Yes" + otherButton:nil + informativeTextWithFormat:@"Failed to load %@ within %ld seconds\nUsing proxy: %@\nError: %@", [request url], (NSInteger)[request timeOutSeconds], [proxy url], (error ? [error localizedDescription] : @"Unknown error")]; + [alert setAlertStyle:NSCriticalAlertStyle]; + if ([alert runModal] == NSAlertDefaultReturn) + [self cancelProxyLoad]; + else + [self failProxyTest]; + } + } + else + { + [self finishProxyTest]; + } } - (void)failProxyTest { - [self returnFromProxyLoadWithError:[NSError errorWithDomain:@"Proxy" code:kProxyLoadFailed userInfo:@{NSLocalizedDescriptionKey: @"Proxy Test Failed"}]]; + [self returnFromProxyLoadWithError:[NSError errorWithDomain:@"Proxy" code:kProxyLoadFailed userInfo:@{NSLocalizedDescriptionKey: @"Proxy Test Failed"}]]; } - (void)finishProxyTest { - NSLog(@"INFO: Proxy test complete."); - [self addToLog:@"INFO: Proxy test complete."]; - [self returnFromProxyLoadWithError:nil]; + NSLog(@"INFO: Proxy test complete."); + [self addToLog:@"INFO: Proxy test complete."]; + [self returnFromProxyLoadWithError:nil]; } - (void)returnFromProxyLoadWithError:(NSError *)error { - if (proxy) - { - NSLog(@"INFO: Using proxy: %@", proxy.url); - [self addToLog:[NSString stringWithFormat:@"INFO: Using proxy: %@", proxy.url]]; - } - else - { - NSLog(@"INFO: No proxy will be used"); - [self addToLog:@"INFO: No proxy will be used"]; - } - [self updateProxyLoadStatus:NO message:nil]; - [self performSelector:[proxyDict[@"selector"] pointerValue] withObject:proxyDict[@"object"] withObject:error]; + if (proxy) + { + NSLog(@"INFO: Using proxy: %@", proxy.url); + [self addToLog:[NSString stringWithFormat:@"INFO: Using proxy: %@", proxy.url]]; + } + else + { + NSLog(@"INFO: No proxy will be used"); + [self addToLog:@"INFO: No proxy will be used"]; + } + [self updateProxyLoadStatus:NO message:nil]; + [proxyDict[@"target"] performSelector:[proxyDict[@"selector"] pointerValue] withObject:proxyDict[@"object"] withObject:error]; } - (void)updateProxyLoadStatus:(BOOL)working message:(NSString *)message { - @try - { - if (working) - { - [currentIndicator setIndeterminate:YES]; - [currentIndicator startAnimation:nil]; - [currentProgress setStringValue:message]; - } - else - { - [currentIndicator setIndeterminate:NO]; - [currentIndicator stopAnimation:nil]; - [currentProgress setStringValue:@""]; - } - } - @catch (NSException *e) { - NSLog(@"NO UI: updateProxyLoadStatus:message:"); - } + @try + { + if (working) + { + [currentIndicator setIndeterminate:YES]; + [currentIndicator startAnimation:nil]; + [currentProgress setStringValue:message]; + } + else + { + [currentIndicator setIndeterminate:NO]; + [currentIndicator stopAnimation:nil]; + [currentProgress setStringValue:@""]; + } + } + @catch (NSException *e) { + NSLog(@"NO UI: updateProxyLoadStatus:message:"); + } +} + +#pragma mark Extended Show Information +- (IBAction)showExtendedInformationForSelectedProgramme:(id)sender { + popover.behavior = NSPopoverBehaviorTransient; + [self addToLog:@"Retrieving Information." :self]; + Programme *programme = searchResultsArray[[searchResultsTable selectedRow]]; + if (programme) { + infoView.alphaValue = 0.1; + loadingView.alphaValue = 1.0; + [retrievingInfoIndicator startAnimation:self]; + + @try { + [popover showRelativeToRect:[searchResultsTable frameOfCellAtColumn:1 row:[searchResultsTable selectedRow]] ofView:(NSView *)searchResultsTable preferredEdge:NSMaxYEdge]; + } + @catch (NSException *exception) { + NSLog(@"%@",[exception description]); + NSLog(@"%@",searchResultsTable); + return; + } + if (!programme.extendedMetadataRetrieved.boolValue) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(informationRetrieved:) name:@"ExtendedInfoRetrieved" object:programme]; + [programme retrieveExtendedMetadata]; + } + else { + [self informationRetrieved:[NSNotification notificationWithName:@"" object:programme]]; + } + } +} +- (void)informationRetrieved:(NSNotification *)note { + Programme *programme = note.object; + + if (programme.successfulRetrieval.boolValue) { + if (programme.thumbnail) + imageView.image = programme.thumbnail; + + if (programme.seriesName) + seriesName.stringValue = programme.seriesName; + else + seriesName.stringValue = @"Unable to Retrieve"; + + if (programme.episodeName) + episodeName.stringValue = programme.episodeName; + else + seriesName.stringValue = @""; + + if (programme.season && programme.episode) + numbersField.stringValue = [NSString stringWithFormat:@"Series: %ld Episode: %ld",(long)programme.season,(long)programme.episode]; + else + numbersField.stringValue = @""; + + if (programme.duration) + durationField.stringValue = [NSString stringWithFormat:@"Duration: %d minutes",programme.duration.intValue]; + else + durationField.stringValue = @""; + + if (programme.categories) + categoriesField.stringValue = [NSString stringWithFormat:@"Categories: %@",programme.categories]; + else + categoriesField.stringValue = @""; + + if (programme.firstBroadcast) + firstBroadcastField.stringValue = [NSString stringWithFormat:@"First Broadcast: %@",[programme.firstBroadcast description]]; + else + firstBroadcastField.stringValue = @""; + + if (programme.lastBroadcast) + lastBroadcastField.stringValue = [NSString stringWithFormat:@"Last Broadcast: %@", [programme.lastBroadcast description]]; + else + lastBroadcastField.stringValue = @""; + + if (programme.desc) + descriptionView.string = programme.desc; + else + descriptionView.string = @""; + + if (programme.modeSizes) + modeSizeController.content = programme.modeSizes; + else + modeSizeController.content = [NSDictionary dictionary]; + + [retrievingInfoIndicator stopAnimation:self]; + infoView.alphaValue = 1.0; + loadingView.alphaValue = 0.0; + [self addToLog:@"Info Retrieved" :self]; + } + else { + [retrievingInfoIndicator stopAnimation:self]; + loadingLabel.stringValue = @"Info could not be retrieved."; + [self addToLog:@"Info could not be retrieved" :self]; + } } @synthesize log_value; @synthesize getiPlayerPath; +@synthesize proxy; @end diff --git a/English.lproj/MainMenu.xib b/English.lproj/MainMenu.xib index 57f7d7a5..f12dcd1d 100644 --- a/English.lproj/MainMenu.xib +++ b/English.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -18,21 +18,34 @@ + + + + + + + + + + + + + @@ -43,9 +56,12 @@ + + + @@ -236,6 +252,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA + @@ -504,11 +521,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -557,7 +574,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + @@ -591,11 +608,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -630,17 +647,17 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + - + @@ -651,7 +668,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + @@ -659,7 +676,22 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + + + + + + + + + + + + + + + + @@ -681,15 +713,15 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + - + @@ -877,11 +909,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -925,11 +957,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -953,7 +985,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + @@ -1023,7 +1055,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + @@ -1665,11 +1697,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -1951,11 +1983,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -1967,11 +1999,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -2005,11 +2037,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -2089,11 +2121,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -2127,11 +2159,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -2221,11 +2253,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -2271,7 +2303,7 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + @@ -2301,11 +2333,11 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + @@ -2453,17 +2485,17 @@ AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA - + - + - + - + @@ -2725,6 +2757,226 @@ DQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Get_iPlayer GUI.xcodeproj/project.pbxproj b/Get_iPlayer GUI.xcodeproj/project.pbxproj index cb352339..347b496e 100644 --- a/Get_iPlayer GUI.xcodeproj/project.pbxproj +++ b/Get_iPlayer GUI.xcodeproj/project.pbxproj @@ -586,7 +586,6 @@ buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Get iPlayer Automator" */; buildPhases = ( D9CCF443102349ED009205BE /* Copy Frameworks */, - D9279DD6182045CA008DBB09 /* ShellScript */, 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, 8D11072E0486CEB800E47090 /* Frameworks */, @@ -684,22 +683,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - D9279DD6182045CA008DBB09 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "LOCATION=\"${BUILT_PRODUCTS_DIR}\"/\"${FRAMEWORKS_FOLDER_PATH}\"\nIDENTITY=\"Developer ID Application: Thomas Willson\"\ncodesign --verbose --force --sign \"$IDENTITY\" \"$LOCATION/Sparkle.framework/Versions/A\""; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Get_iPlayer GUI.xcodeproj/project.xcworkspace/xcuserdata/thomaswillson.xcuserdatad/UserInterfaceState.xcuserstate b/Get_iPlayer GUI.xcodeproj/project.xcworkspace/xcuserdata/thomaswillson.xcuserdatad/UserInterfaceState.xcuserstate index 6c9a217d8c5c76ef057dc60ad209e0ce5d314b69..737f84dbd1b93b69d059554cd03a87a4aaf2e20a 100644 GIT binary patch literal 56701 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 literal 50529 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 b-m{@+YtOF#;Xflr{4b}T{a^n7?b-8x7oH)T diff --git a/Programme.h b/Programme.h index 80fafbca..6f0a6817 100644 --- a/Programme.h +++ b/Programme.h @@ -6,7 +6,7 @@ // Copyright 2009 __MyCompanyName__. All rights reserved. // -#import +#import @interface Programme : NSObject { @@ -34,12 +34,25 @@ NSNumber *podcast; //Extended Metadata + NSNumber *extendedMetadataRetrieved; + NSNumber *successfulRetrieval; NSNumber *duration; + NSString *categories; + NSDate *__strong firstBroadcast; + NSDate *__strong lastBroadcast; + NSDictionary *modeSizes; + NSImage *thumbnail; + + NSMutableString *taskOutput; + NSPipe *pipe; + volatile bool taskRunning; + } - (id)initWithInfo:(id)sender pid:(NSString *)PID programmeName:(NSString *)SHOWNAME network:(NSString *)TVNETWORK; - (id)initWithShow:(Programme *)show; - (void)printLongDescription; +- (void)retrieveExtendedMetadata; @property (readwrite) NSString *showName; @property (readwrite) NSString *tvNetwork; @@ -63,4 +76,13 @@ @property (readwrite, strong) NSDate *dateAired; @property (readwrite) NSString *desc; @property (readwrite) NSNumber *podcast; + +@property (readwrite) NSNumber *extendedMetadataRetrieved; +@property (readwrite) NSNumber *successfulRetrieval; +@property (readwrite) NSNumber *duration; +@property (readwrite) NSString *categories; +@property (readwrite) NSDate *firstBroadcast; +@property (readwrite) NSDate *lastBroadcast; +@property (readwrite) NSDictionary *modeSizes; +@property (readwrite) NSImage *thumbnail; @end diff --git a/Programme.m b/Programme.m index 1bcbb083..dd8ba68f 100644 --- a/Programme.m +++ b/Programme.m @@ -8,7 +8,10 @@ #import "Programme.h" #import "NSString+HTML.h" -extern BOOL runDownloads; +#import "AppController.h" +#import "HTTPProxy.h" +#import "ASIHTTPRequest.h" +//extern bool runDownloads; @implementation Programme @@ -29,10 +32,11 @@ - (id)initWithInfo:(id)sender pid:(NSString *)PID programmeName:(NSString *)SHOW radio = @NO; subtitlePath=[[NSString alloc] init]; realPID=[[NSString alloc] init]; - reasonForFailure=[[NSString alloc] init]; - availableModes=[[NSString alloc] init]; - desc=[[NSString alloc] init]; - podcast=@NO; + reasonForFailure=[[NSString alloc] init]; + availableModes=[[NSString alloc] init]; + desc=[[NSString alloc] init]; + podcast=@NO; + extendedMetadataRetrieved=@NO; return self; } - (id)initWithShow:(Programme *)show @@ -51,10 +55,11 @@ - (id)initWithShow:(Programme *)show radio = [show radio]; realPID = [show realPID]; subtitlePath = [show subtitlePath]; - reasonForFailure=[show reasonForFailure]; - availableModes=[[NSString alloc] init]; - desc=[[NSString alloc] init]; - podcast = [show podcast]; + reasonForFailure=[show reasonForFailure]; + availableModes=[[NSString alloc] init]; + desc=[[NSString alloc] init]; + podcast = [show podcast]; + extendedMetadataRetrieved=@NO; return self; } - (id)init @@ -78,13 +83,14 @@ - (id)init path = @"Unknown"; processedPID = @NO; radio = @NO; - url = [[NSString alloc] init]; + url = [[NSString alloc] init]; realPID=[[NSString alloc] init]; subtitlePath=[[NSString alloc] init]; - reasonForFailure=[[NSString alloc] init]; - availableModes=[[NSString alloc] init]; - desc=[[NSString alloc] init]; - podcast=@NO; + reasonForFailure=[[NSString alloc] init]; + availableModes=[[NSString alloc] init]; + desc=[[NSString alloc] init]; + podcast=@NO; + extendedMetadataRetrieved=@NO; return self; } - (id)description @@ -104,8 +110,8 @@ - (void) encodeWithCoder: (NSCoder *)coder [coder encodeObject:processedPID forKey:@"processedPID"]; [coder encodeObject:radio forKey:@"radio"]; [coder encodeObject:realPID forKey:@"realPID"]; - [coder encodeObject:url forKey:@"url"]; - [coder encodeObject:podcast forKey:@"podcast"]; + [coder encodeObject:url forKey:@"url"]; + [coder encodeObject:podcast forKey:@"podcast"]; } - (id) initWithCoder: (NSCoder *)coder { @@ -123,39 +129,221 @@ - (id) initWithCoder: (NSCoder *)coder processedPID = [coder decodeObjectForKey:@"processedPID"]; radio = [coder decodeObjectForKey:@"radio"]; realPID = [coder decodeObjectForKey:@"realPID"]; - url = [coder decodeObjectForKey:@"url"]; + url = [coder decodeObjectForKey:@"url"]; subtitlePath=[[NSString alloc] init]; - reasonForFailure=[[NSString alloc] init]; - availableModes=[[NSString alloc] init]; - desc=[[NSString alloc] init]; - podcast = [coder decodeObjectForKey:@"podcast"]; + reasonForFailure=[[NSString alloc] init]; + availableModes=[[NSString alloc] init]; + desc=[[NSString alloc] init]; + podcast = [coder decodeObjectForKey:@"podcast"]; + extendedMetadataRetrieved=@NO; return self; } /* -- (id)pasteboardPropertyListForType:(NSString *)type + - (id)pasteboardPropertyListForType:(NSString *)type + { + if ([type isEqualToString:@"com.thomaswillson.programme"]) + { + return [NSKeyedArchiver archivedDataWithRootObject:self]; + } + } + - (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard + { + return [NSArray arrayWithObject:@"com.thomaswillson.programme"]; + } + */ +-(void)setPid:(NSString *)newPID { - if ([type isEqualToString:@"com.thomaswillson.programme"]) - { - return [NSKeyedArchiver archivedDataWithRootObject:self]; + self->pid = [newPID stringByReplacingOccurrencesOfString:@"amp;" withString:@""]; +} +-(NSString *)pid +{ + return pid; +} +-(void)printLongDescription +{ + NSLog(@"%@:\n TV Network: %@\n Processed PID: %@\n Real PID: %@\n Available Modes: %@\n URL: %@\n", + showName,tvNetwork,processedPID,realPID,availableModes,url); +} + +-(void)retrieveExtendedMetadata +{ + [[AppController sharedController] addToLog:@"Retrieving Extended Metadata" :self]; + [[AppController sharedController] loadProxyInBackgroundForSelector:@selector(proxyRetrievalFinished:proxyError:) withObject:nil onTarget:self]; +} + +-(void)proxyRetrievalFinished:(id)sender proxyError:(NSError *)proxyError +{ + taskOutput = [[NSMutableString alloc] init]; + NSTask *task = [[NSTask alloc] init]; + pipe = [[NSPipe alloc] init]; + + [task setLaunchPath:@"/usr/bin/perl"]; + NSMutableArray *args = [NSMutableArray arrayWithArray:@[[[NSBundle mainBundle] pathForResource:@"get_iplayer" ofType:@"pl"], + @"--nopurge", + @"--nocopyright", + @"-e60480000000000000", + @"-i", + [NSString stringWithFormat:@"--profile-dir=%@",[@"~/Library/Application Support/Get iPlayer Automator/" stringByExpandingTildeInPath]],pid]]; + if ([AppController sharedController].proxy) { + [args addObject:[AppController sharedController].proxy.url]; + + if (![[[NSUserDefaults standardUserDefaults] valueForKey:@"AlwaysUseProxy"] boolValue]) + { + [args addObject:@"--partial-proxy"]; + } + + } + + [task setArguments:args]; + + [task setStandardOutput:pipe]; + NSFileHandle *fh = [pipe fileHandleForReading]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataRetrievalDataReady:) name:NSFileHandleReadCompletionNotification object:fh]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataRetrievalFinished:) name:NSTaskDidTerminateNotification object:task]; + + [task launch]; + [fh readInBackgroundAndNotify]; +} + +-(void)metadataRetrievalDataReady:(NSNotification *)n +{ + NSData *d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; + + if ([d length] > 0) { + NSString *s = [[NSString alloc] initWithData:d + encoding:NSUTF8StringEncoding]; + + [taskOutput appendString:s]; + [[AppController sharedController] addToLog:s :self]; + [[pipe fileHandleForReading] readInBackgroundAndNotify]; } + else { + [self metadataRetrievalFinished:nil]; + } } -- (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard + +-(void)metadataRetrievalFinished:(NSNotification *)n { - return [NSArray arrayWithObject:@"com.thomaswillson.programme"]; + taskRunning=NO; + categories = [self scanField:@"categories" fromList:taskOutput]; + + NSString *descTemp = [self scanField:@"desc" fromList:taskOutput]; + if (descTemp) { + desc = descTemp; + } + + NSString *durationTemp = [self scanField:@"duration" fromList:taskOutput]; + if (durationTemp) { + if ([durationTemp hasSuffix:@"min"]) + duration = [NSNumber numberWithInteger:[durationTemp integerValue]]; + else + duration = [NSNumber numberWithInteger:[durationTemp integerValue]/60]; + } + + firstBroadcast = [self processDate:[self scanField:@"firstbcast" fromList:taskOutput]]; + lastBroadcast = [self processDate:[self scanField:@"lastbcast" fromList:taskOutput]]; + + seriesName = [self scanField:@"longname" fromList:taskOutput]; + + episodeName = [self scanField:@"episode" fromList:taskOutput]; + + NSString *seasonNumber = [self scanField:@"seriesnum" fromList:taskOutput]; + if (seasonNumber) { + season = [seasonNumber integerValue]; + } + + NSString *episodeNumber = [self scanField:@"episodenum" fromList:taskOutput]; + if (episodeNumber) { + episode = [episodeNumber integerValue]; + } + NSString *modeSizesString = [self scanField:@"modesizes" fromList:taskOutput]; + if (modeSizesString) { + NSScanner *sizeScanner = [NSScanner scannerWithString:modeSizesString]; + [sizeScanner scanString:@"default:" intoString:nil]; + NSString *newSizesString; + [sizeScanner scanUpToString:@":" intoString:&newSizesString]; + + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[a-z]*[0-2]=[0-9]*MB" options:0 error:nil]; + NSArray *matches = [regex matchesInString:newSizesString options:0 range:NSMakeRange(0, [newSizesString length])]; + if ([matches count] > 0) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + for (NSTextCheckingResult *modesizeResult in matches) { + NSString *modesize = [newSizesString substringWithRange:modesizeResult.range]; + if ([modesize hasPrefix:@"rtsp"] || [modesize hasPrefix:@"wma"]) { + continue; + } + NSArray *comps = [modesize componentsSeparatedByString:@"="]; + if ([comps count] == 2) { + [dictionary setObject:comps[1] forKey:comps[0]]; + } + } + modeSizes = dictionary; + } + } + NSString *thumbURL = [self scanField:@"thumbnail4" fromList:taskOutput]; + if (!thumbURL) { + thumbURL = [self scanField:@"thumbnail" fromList:taskOutput]; + } + if (thumbURL) { + NSLog(@"URL: %@", thumbURL); + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:thumbURL]]; + [request setDelegate:self]; + [request setDidFinishSelector:@selector(thumbnailRequestFinished:)]; + [request setDidFailSelector:@selector(thumbnailRequestFinished:)]; + [request setTimeOutSeconds:3]; + [request setNumberOfTimesToRetryOnTimeout:3]; + [request startAsynchronous]; + } } - */ --(void)setPid:(NSString *)newPID + +- (void)thumbnailRequestFinished:(ASIHTTPRequest *)request { - self->pid = [newPID stringByReplacingOccurrencesOfString:@"amp;" withString:@""]; + if (request.responseStatusCode == 200) { + thumbnail = [[NSImage alloc] initWithData:request.responseData]; + } + successfulRetrieval = @YES; + extendedMetadataRetrieved = @YES; + [[NSNotificationCenter defaultCenter] postNotificationName:@"ExtendedInfoRetrieved" object:self]; + } --(NSString *)pid + +-(NSString *)scanField:(NSString *)field fromList:(NSString *)list { - return pid; + NSString __autoreleasing *buffer; + + NSScanner *scanner = [NSScanner scannerWithString:list]; + [scanner scanUpToString:[NSString stringWithFormat:@"%@:",field] intoString:nil]; + [scanner scanString:[NSString stringWithFormat:@"%@:",field] intoString:nil]; + [scanner scanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:nil]; + [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&buffer]; + + return [buffer copy]; } --(void)printLongDescription + +-(NSDate *)processDate:(NSString *)date { - NSLog(@"%@:\n TV Network: %@\n Processed PID: %@\n Real PID: %@\n Available Modes: %@\n URL: %@\n", - showName,tvNetwork,processedPID,realPID,availableModes,url); + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_8) //10.8, 10.9 + [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZZZZZ"]; + else //10.7 + [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZZ"]; + + if (date) { + date = [self scanField:@"default" fromList:date]; + if (date) { + if (NSAppKitVersionNumber <= NSAppKitVersionNumber10_8) { //Before 10.8 doesn't recognize the Z + if ([date hasSuffix:@"Z"]) { + date = [date stringByReplacingOccurrencesOfString:@"Z" withString:@"+00:00"]; + } + } + if (NSAppKitVersionNumber < NSAppKitVersionNumber10_8) { + date = [date stringByReplacingCharactersInRange:NSMakeRange(date.length - 3, 1) withString:@""]; + } + return [dateFormatter dateFromString:date]; + } + } + return nil; } @synthesize showName; @@ -179,4 +367,13 @@ -(void)printLongDescription @synthesize dateAired; @synthesize desc; @synthesize podcast; + +@synthesize extendedMetadataRetrieved; +@synthesize successfulRetrieval; +@synthesize duration; +@synthesize categories; +@synthesize firstBroadcast; +@synthesize lastBroadcast; +@synthesize modeSizes; +@synthesize thumbnail; @end