// // CellLocationsController.m // CellLocations // // Created by Simon Urbanek on 4/22/11. // Copyright 2011 Simon Urbanek. All rights reserved. // #import "CellLocationsController.h" static CellLocationsController *sharedController; @interface MBackupWithKey : MBackup { NSString *key; } - (id) initWithPath: (NSString*) aPath substring: (NSString*) aSubstring; - (NSString*) key; @end @implementation MBackupWithKey - (id) initWithPath: (NSString*) aPath substring:(NSString*) aSubstring { if ((self = [super initWithPath:aPath])) { key = [self keyForPathContaining:aSubstring]; if (key) [key retain]; } return self; } - (NSString*) key { return key; } @end @implementation CellLocationsController @synthesize stripTimestamps, selectionIsValid, keepTmpDir, extracted; + (CellLocationsController*) sharedController { return sharedController; } - (id) init { self = [super init]; if (self != nil) { backups = [[NSMutableArray alloc] init]; } return self; } - (void) dealloc { [backups release]; if (tmpdir) [tmpdir release]; [super dealloc]; } - (void) appendInfo: (NSString*) aString { [infoView setString:[[infoView string] stringByAppendingString:aString]]; } - (void)awakeFromNib { sharedController = self; NSFileManager *fm = [[NSFileManager alloc] init]; // set up temporary directory tmpdir = [[NSString stringWithFormat:@"/tmp/cell.locations.tmp.%d", getuid()] retain]; [fm removeItemAtPath:tmpdir error:nil]; [fm createDirectoryAtPath:tmpdir withIntermediateDirectories:NO attributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0700] forKey:NSFilePosixPermissions] error:nil]; [infoView setString:[NSString stringWithFormat:@"Created temporary directory: %@\nIt is only readable by you and will hold any data you extract.\nLooking for backups...\n", tmpdir]]; // discover backup files NSError *err = nil; NSString *bakTopLevel = [@"~/Library/Application Support/MobileSync/Backup"stringByExpandingTildeInPath]; NSArray *bs = [fm contentsOfDirectoryAtPath:bakTopLevel error:&err]; if (!bs) [self appendInfo:[NSString stringWithFormat:@"Sorry, I could not find and mobile device backups on your machine.\n(%@)\n", err]]; else { for (NSString *bDirectoryName in bs) { NSString *bDirectory = [NSString stringWithFormat:@"%@/%@", bakTopLevel, bDirectoryName]; if ([fm fileExistsAtPath:[bDirectory stringByAppendingString:@"/Info.plist"]]) { MBackupWithKey *bak = [[MBackupWithKey alloc] initWithPath:bDirectory substring:@"consolidated.db"]; if (bak && [bak hasInfo]) { NSDictionary *info = [bak info]; NSString *key = [bak key]; [self appendInfo:[NSString stringWithFormat:@"Found backup of '%@' (%@, iOS %@, %@) %@\n", [info objectForKey:@"Display Name"], [info objectForKey:@"Product Type"], [info objectForKey:@"Product Version"], [info objectForKey:@"Phone Number"], key ? @"OK" : @"consolidated.db is MISSING"]]; if (key) { [backups addObject:bak]; } } [bak release]; } } } [fm release]; // make sure we're fresh [backupsTableView reloadData]; } - (void)applicationWillTerminate:(NSNotification *)notification { // remove temporary files on exit if (!self.keepTmpDir && tmpdir) [[NSFileManager defaultManager] removeItemAtPath:tmpdir error:nil]; } - (void)windowWillClose:(NSNotification *)notification { [[NSApplication sharedApplication] terminate:self]; } // -- actual action - (IBAction) fetchCellLocations: (id) sender { NSUInteger i, n = [backups count]; NSIndexSet *set = [backupsTableView selectedRowIndexes]; NSFileManager *fm = [NSFileManager defaultManager]; for (i = 0; i < n; i++) { NSString *resFile = [tmpdir stringByAppendingFormat:@"/CellLocation.%d.psv", i]; [fm removeItemAtPath:resFile error:nil]; if ([set containsIndex:i]) { MBackupWithKey *bak = (MBackupWithKey*) [backups objectAtIndex:i]; NSString *key = [bak key]; if (key) { NSString *dbPath = [[bak path] stringByAppendingPathComponent:key]; NSString *cmd = [NSString stringWithFormat:@"/usr/bin/sqlite3 '%@' 'select * from CellLocation;' > '%@'", dbPath, resFile]; [self appendInfo:[cmd stringByAppendingString:@"\n"]]; system([[NSString stringWithFormat:@"/usr/bin/sqlite3 '%@' 'select * from CellLocation;' > '%@'", dbPath, resFile] UTF8String]); if (![fm fileExistsAtPath:resFile]) [self appendInfo:[NSString stringWithFormat:@"ERROR: cannot extract CellLocation table for %@\n", [[bak info] objectForKey:@"Display Name"]]]; } } } // collect results system([[NSString stringWithFormat:@"%@ '%@'/CellLocation.*.psv|sort|uniq > '%@/filtered.psv'", self.stripTimestamps ? @"awk -F\\| -v OFS=\\| '{print $1,$2,$3,$4,$6,$7,$8,$13}'" : @"cat", tmpdir, tmpdir] UTF8String]); NSData *resCont = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/filtered.psv", tmpdir]]; if (!resCont) [self appendInfo:@"ERROR: cannot read result file"]; else if ([resCont length] == 0) [self appendInfo:@"ERROR: result file is empty"]; else { NSString *resString = [[NSString alloc] initWithData:resCont encoding:NSUTF8StringEncoding]; if (!resString) [self appendInfo:@"ERROR: result file is not a valid UTF8 file"]; else { [infoView setString:resString]; self.extracted = YES; [[infoView window] makeFirstResponder:infoView]; [resString release]; } } } // -- list of backups - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { return [backups count]; } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { NSString *cid = [aTableColumn identifier]; if (rowIndex >= 0 && rowIndex < [backups count]) { MBackupWithKey *bak = [backups objectAtIndex:rowIndex]; NSDictionary *info = [bak info]; if ([cid isEqual:@"name"]) { NSString *str = [info objectForKey:@"Display Name"]; if (str) return str; } if ([cid isEqual:@"info"]) { return [NSString stringWithFormat:@"+%@, %@, iOS %@", [info objectForKey:@"Phone Number"], [info objectForKey:@"Product Type"], [info objectForKey:@"Product Version"]]; } if ([cid isEqual:@"id"]) return [bak key]; } return @""; } - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { self.selectionIsValid = [[backupsTableView selectedRowIndexes] count] > 0; } @end