Recipe: Push in Action
Once you set up a client such as the one discussed in Recipe 16-1 and routines like Recipe 16-2 that let you send notifications, it's time to think about deploying an actual service. Recipe 16-3 introduces a Twitter client that repeatedly scans a search.twitter.com RSS feed and pushes notifications whenever a new tweet is found (see Figure 16-13).
Recipe 16-3. Wrapping Remote Notifications into a Simple Twitter Utility
#define TWEET_FILE [NSHomeDirectory() stringByAppendingPathComponent:@".tweet"] #define URL_STRING @"http://search.twitter.com/search.atom?q=+ericasadun" #define SHOW_TICK NO #define CAL_FORMAT @%Y-%m-%dT%H:%M:%SZ" int main (int argc, const char * argv[]) { if (argc < 2) { printf("Usage: %s delay-in-seconds\n", argv[0]); exit(-1); } NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Fetch certificate and device information from the current // directory as set up with pushutil char wd[256]; getwd(wd); NSString *cwd = [NSString stringWithCString:wd]; NSArray *contents = [[NSFileManager defaultManager] directoryContentsAtPath:cwd]; NSArray *dfiles = [contents pathsMatchingExtensions: [NSArray arrayWithObject:@"devices"]]; if (![dfiles count]) { printf("Error retrieving device token\n"); exit(-1); } NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: [cwd stringByAppendingPathComponent:[dfiles lastObject]]]; if (!dict || ([[dict allKeys] count] < 1)) { printf("Error retrieving device token\n"); exit(-1); } [APNSHelper sharedInstance].deviceTokenID = [dict objectForKey: [[dict allKeys] objectAtIndex:0]]; NSArray *certs = [contents pathsMatchingExtensions: [NSArray arrayWithObject:@"cer"]]; if ([certs count] < 1) { printf("Error finding SSL certificate\n"); exit(-1); } NSString *certPath = [certs lastObject]; NSData *dCert = [NSData dataWithContentsOfFile:certPath]; if (!dCert) { printf("Error retrieving SSL certificate\n"); exit(-1); } [APNSHelper sharedInstance].certificateData = dCert; // Set up delay int delay = atoi(argv[1]); printf("Initializing with delay of %d\n", delay); // Set up dictionaries NSMutableDictionary *mainDict = [NSMutableDictionary dictionary]; NSMutableDictionary *payloadDict = [NSMutableDictionary dictionary]; NSMutableDictionary *alertDict = [NSMutableDictionary dictionary]; [mainDict setObject:payloadDict forKey:@"aps"]; [payloadDict setObject:alertDict forKey:@"alert"]; [payloadDict setObject:@"ping1.caf" forKey:@"sound"]; [alertDict setObject:[NSNull null] forKey:@"action-loc-key"]; while (1 > 0) { NSAutoreleasePool *wadingpool = [[NSAutoreleasePool alloc] init]; TreeNode *root = [[XMLParser sharedInstance] parseXMLFromURL: [NSURL URLWithString:URL_STRING]]; TreeNode *found = [root objectForKey:@"entry"]; if (found) { // Recover the string to tweet NSString *tweetString = [NSString stringWithFormat: @"%@-%@", [found leafForKey:@"name"], [found leafForKey:@"title"]]; // Recover pubbed date NSString *dateString = [found leafForKey:@"published"]; NSCalendarDate *date = [NSCalendarDate dateWithString: dateString calendarFormat:CAL_FORMAT]; // Recover stored date NSString *prevDateString = [NSString stringWithContentsOfFile: TWEET_FILE encoding:NSUTF8StringEncoding error:nil]; NSCalendarDate *pDate = [NSCalendarDate dateWithString: prevDateString calendarFormat:CAL_FORMAT]; // Tweet only if there is either no stored date or // the dates are not equal if (!pDate || ![pDate isEqualToDate:date]) { // Update with the new tweet information NSLog(@"\nNew tweet from %\n \"%@\"\n\n", [found leafForKey:@"name"], [found leafForKey:@"title"]); // Store the tweet time [dateString writeToFile:TWEET_FILE atomically:YES encoding:NSUTF8StringEncoding error:nil]; // push it [alertDict setObject:jsonescape(tweetString) forKey:@"body"]; [[APNSHelper sharedInstance] push: [JSONHelper jsonWithDict:mainDict]]; } } root = nil; found = nil; [wadingpool drain]; [NSThread sleepForTimeInterval:(double) delay]; if (SHOW_TICK) printf("tick\n"); } [pool drain]; return 0; }
Figure 16-13 Twitter provides an ideal way to test a polled RSS feed.
This code is built around the push routine from Recipe 16-2 and the XML parser from Recipe 13-13. This utility pulls down Twitter search data as an XML tree and finds the first tree node of the type "entry," which is how Twitter stores each tweet.
Next, it creates a string by combining the poster name (from the "name" leaf) and the post contents (from the "title" leaf). It then adds a JSON-escaped version of this string to the aps > alert dictionary as the message body. The alert sound and one-button style are fixed in the main aps payload dictionary.
The application runs in a loop with a time delay set by a command-line argument. Every n seconds (determined by the second command-line argument), it polls, parses, and checks for a new tweet, and if it finds one, pushes it out through APNS. Figure 16-13 shows this utility in action, displaying a tweet alert on the client iPhone.