// // NYTRulesWindowController.m // Notifications for YouTube // // Created by Kim Wittenburg on 21.07.13. // Copyright (c) 2013 Kim Wittenburg. All rights reserved. // #import "NYTRulesWindowController.h" // Core Imports #import "NYTUser.h" #import "NYTUtil.h" #import "NYTAuthentication.h" // Other Imports #import "NYTChannelRestriction.h" @interface NYTRulesWindowController () @property (readwrite) BOOL loading; @property (readwrite) NSArray *subscriptions; /* The restrictions previously stored in the user defaults */ @property NSDictionary *savedRestrictions; @end @implementation NYTRulesWindowController /* Convenient init */ - (id)init { return [self initWithWindowNibName:@"NYTRulesWindowController" owner:self]; } - (void)windowDidLoad { [super windowDidLoad]; // Load the subscriptions [self loadSubscriptions]; } #pragma mark *** Loading *** - (void)loadSubscriptions { self.loading = YES; // Load in background to not block the main thread dispatch_queue_t prevQueue = dispatch_get_current_queue(); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Prepare for multiple pages NSError *error; NSMutableArray *loadedSubscriptions; NSUInteger processedResults = 0; NSUInteger totalResults = 0; do { // We are creating a couple of objects so setup a separate autoreleasepool @autoreleasepool { // Load the document NSString *url = [NSString stringWithFormat:@"https://gdata.youtube.com/feeds/api/users/default/subscriptions?max-results=50&start-index=%li&v=2", processedResults+1]; NSXMLDocument *document = LoadXMLDocumentSynchronous(url, [NYTAuthentication sharedAuthentication].authentication, &error); // Store the entries as app-friendly video objects if (document) { // Set up the collection for collecting the videos if (processedResults == 0) { static NSString *const totalResultsPath = @"./feed/openSearch:totalResults"; NSXMLElement *totalResultsElement = [document nodesForXPath:totalResultsPath error:nil][0]; totalResults = (NSUInteger) totalResultsElement.stringValue.integerValue; loadedSubscriptions = [NSMutableArray arrayWithCapacity:totalResults]; } // For each page parse its results and add them to the collection static NSString *const resultsPerPagePath = @"./feed/openSearch:itemsPerPage"; NSXMLElement *resultsPerPageElement = [document nodesForXPath:resultsPerPagePath error:nil][0]; int resultsPerPage = resultsPerPageElement.stringValue.intValue; processedResults += resultsPerPage; NSArray *pageSubscriptions = [self subscriptionsOnPage:document error:&error]; if (pageSubscriptions) { [loadedSubscriptions addObjectsFromArray:pageSubscriptions]; } else { // Abort on error [self abortLoadingOnQueue:prevQueue withError:error]; return; } } else { // Abort on error [self abortLoadingOnQueue:prevQueue withError:error]; return; } } } while (processedResults < totalResults); // Finish the loading process on the main thread dispatch_async(prevQueue, ^{ self.subscriptions = loadedSubscriptions; self.loading = NO; }); }); } - (NSArray *)subscriptionsOnPage:(NSXMLDocument *)document error:(NSError **)error { // Set up the stored restrictions (but just once) if (!self.savedRestrictions) { self.savedRestrictions = [[[NSUserDefaults standardUserDefaults] objectForKey:@"Rules"] mutableCopy]; if (!self.savedRestrictions) { self.savedRestrictions = [NSMutableDictionary new]; } } // For every entry parse a subscription NSArray *entries = [document nodesForXPath:@"./feed/entry" error:nil]; NSMutableArray *subscriptions = [NSMutableArray arrayWithCapacity:entries.count]; for (NSXMLNode *entry in entries) { NYTUser *user = [[NYTUser alloc] initWithSubscriptionEntry:entry]; // Have a restriction object available for each subscription. If there is none stored create a default one. NSData *restrictionData = self.savedRestrictions[user.channelID]; NYTChannelRestriction *restriction; if (restrictionData) { restriction = [NSKeyedUnarchiver unarchiveObjectWithData:restrictionData]; } else { restriction = [[NYTChannelRestriction alloc] init]; } restriction.user = user; [subscriptions addObject:restriction]; } return subscriptions; } - (void)abortLoadingOnQueue:(dispatch_queue_t)queue withError:(NSError *)error { dispatch_async(queue, ^{ [NSApp presentError:error modalForWindow:self.window delegate:self didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:) contextInfo:NULL]; }); } - (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)contextInfo { if (!didRecover) { [self cancel:nil]; } } - (IBAction)enableAllNotifications:(id)sender { // Really enable all (also clearing the predicate) for (NYTChannelRestriction *restriction in self.subscriptions) { restriction.disableAllNotifications = NO; restriction.predicate = nil; } } - (IBAction)disableAllNotifications:(id)sender { for (NYTChannelRestriction *restriction in self.subscriptions) { restriction.disableAllNotifications = YES; } } - (IBAction)save:(id)sender { // Encode and save the restrictions that are not default values. NSMutableDictionary *restrictsToSave = [NSMutableDictionary new]; for (NYTChannelRestriction *restrictions in self.subscriptions) { if (restrictions.differsFromDefaultValues) { restrictsToSave[restrictions.user.channelID] = [NSKeyedArchiver archivedDataWithRootObject:restrictions]; } } [[NSUserDefaults standardUserDefaults] setObject:restrictsToSave forKey:@"Rules"]; [[NSUserDefaults standardUserDefaults] synchronize]; // Hide this window [self cancel:sender]; } - (IBAction)cancel:(id)sender { // Just hide the window without saving [self.window orderOut:sender]; [NSApp endSheet:self.window]; } @end