/** * Glitter Auto Update Object * Glitter is an automatic update class for your iPhone application. It requires no package managers * to be installed and thus is superior to other auto update classes available. * * (c) 2008 TheSpicyChicken **/ #import "Glitter.h" #define GlitterWorkDir [NSHomeDirectory() stringByAppendingPathComponent:@"Glitter"] #define SystemOuputFile @"/tmp/glitter.out" @implementation Glitter - (id)initWithApplication:(UIApplication *)application URL:(NSString *)infoURL { if ((self = [super init])) { _application = application; _infoURL = infoURL; _updateReady = NO; if (![[NSFileManager defaultManager] fileExistsAtPath:GlitterWorkDir]) { NSLog(@"Creating Glitter working directory."); if (![[NSFileManager defaultManager] createDirectoryAtPath:GlitterWorkDir attributes:nil]) { NSLog(@"Failed to create folder at %@",GlitterWorkDir); return NO; } } } return self; } - (BOOL)updateReady { return _updateReady; } - (void)checkForUpdateNow { [self checkForUpdateNow:nil]; } /** * Intended to be called from thread. **/ - (void)checkForUpdateNow:(NSDictionary *)updatePlist { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; _updateReady = NO; if (updatePlist == nil) { updatePlist = [[self dictionaryAtURL:_infoURL] objectForKey:[_application version]]; } else { updatePlist = [updatePlist objectForKey:[_application version]]; } if (!updatePlist) { NSLog(@"An update record for version %@ was not found.",[_application version]); return; } if (![[updatePlist objectForKey:@"NeedsUpdate"] boolValue]) { NSLog(@"No update necessary for version %@",[_application version]); return; } NSLog(@"Update required. Checking if already downloaded..."); NSString *archiveName = [[updatePlist objectForKey:@"Archive"] lastPathComponent]; NSString *pathToArchive = [NSString stringWithFormat:@"%@/%@",GlitterWorkDir,archiveName]; BOOL download = YES; if ([[NSFileManager defaultManager] fileExistsAtPath:pathToArchive]) { download = NO; NSLog(@"Download found. Verifying checksum."); if (![self md5IsValid:pathToArchive md5:[updatePlist objectForKey:@"md5"]]) { NSLog(@"Checksum is invalid, redownloading..."); download = YES; } } if (download) { NSURL *archiveURL = [NSURL URLWithString:[updatePlist objectForKey:@"Archive"]]; NSData *data = [NSData dataWithContentsOfURL:archiveURL]; if ([data length] == 0) { NSLog(@"There was an error downloading the file from %@",archiveURL); return; } NSLog(@"Download complete. Saving to %@.",pathToArchive); if (![[NSFileManager defaultManager] createFileAtPath:pathToArchive contents:data attributes:nil]) { NSLog(@"There was an error creating %@",pathToArchive); return; } } if (![self md5IsValid:pathToArchive md5:[updatePlist objectForKey:@"md5"]]) { NSLog(@"md5 hash is invalid."); return; } // Save class variables. _working = [[NSString stringWithFormat:@"%@/%@",GlitterWorkDir,[archiveName stringByDeletingPathExtension]] retain]; _pathToArchive = [pathToArchive retain]; _updatePlist = [updatePlist retain]; _updateReady = YES; [pool release]; } /** * Performs the installation process. **/ - (void)install { [self execute:[NSString stringWithFormat:@"unzip -q -o %@ -d %@",_pathToArchive,_working]]; [self executeInstallationScript:[_updatePlist objectForKey:@"Exec"] fromPath:_working]; NSLog(@"Cleaning up."); NSLog(@"Deleting %@",_working); if (![[NSFileManager defaultManager] removeFileAtPath:_working handler:nil]) NSLog(@"Failed to delete working directory."); NSLog(@"Deleting %@",_pathToArchive); if (![[NSFileManager defaultManager] removeFileAtPath:_pathToArchive handler:nil]) NSLog(@"Failed to delete archive file."); } /** * Performs the actions described in the Exec Array. **/ - (void)executeInstallationScript:(NSArray *)executeArray fromPath:(NSString *)working { NSEnumerator *en = [executeArray objectEnumerator]; NSDictionary *command; while ((command = [en nextObject])) { if ([[command objectForKey:@"Command"] isEqualToString:@"Move"]) { NSString *from = [working stringByAppendingPathComponent:[command objectForKey:@"From"]]; NSString *to = [command objectForKey:@"To"]; NSLog(@"Moving %@ to %@",from,to); [self copyFrom:from to:to]; } if ([[command objectForKey:@"Command"] isEqualToString:@"System"]) { NSLog(@"Executing system command: %@",[command objectForKey:@"Execute"]); [self execute:[command objectForKey:@"Execute"]]; } } } - (NSDictionary *)dictionaryAtURL:(NSString *)urlString { urlString = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)urlString, NULL, NULL, CFStringConvertNSStringEncodingToEncoding(NSASCIIStringEncoding)); NSURL *url = [NSURL URLWithString:urlString]; if (!url) { NSLog(@"Error building url %@ %@",url,urlString); } NSDictionary *tmp = [[NSDictionary alloc] initWithContentsOfURL:url]; return tmp; } - (BOOL)md5IsValid:(NSString *)pathToArchive md5:(NSString *)md5 { NSString *cmd = [NSString stringWithFormat:@"/sbin/md5 %@ > %@",pathToArchive,SystemOuputFile]; [self execute:cmd]; NSString *result = [[NSString stringWithContentsOfFile:SystemOuputFile encoding:NSUTF8StringEncoding error:NULL] substringToIndex:32]; if ([result isEqualToString:md5]) return YES; return NO; } - (int)execute:(NSString *)command { NSLog(@"Executing %@",command); return system([command UTF8String]); } - (void)copyFrom:(NSString *)from to:(NSString *)to { BOOL isDir; if ([[NSFileManager defaultManager] fileExistsAtPath:from isDirectory:&isDir]) { if (isDir) [self copyFolderAtPath:from toPath:to]; else [self copyFileAtPath:from toPath:to]; } } /** * Copy file. **/ - (void)copyFileAtPath:(NSString *)from toPath:(NSString *)to { NSData *sourceData = [NSData dataWithContentsOfFile:from]; if ([sourceData length] > 0) { if (![[NSFileManager defaultManager] createFileAtPath:to contents:sourceData attributes:nil]) NSLog(@"There was an error copying from %@ to %@",from,to); } } /** * This is the only way I can think of to copy a directory tree recursively. * since when I try to use "mv" I get a permission denied error. **/ - (void)copyFolderAtPath:(NSString *)from toPath:(NSString *)to { if (![[NSFileManager defaultManager] fileExistsAtPath:to]) { if (![[NSFileManager defaultManager] createDirectoryAtPath:to attributes:nil]) { NSLog(@"There was an error when attempting to create directory at %@",to); } } NSArray *directoryContents = [[NSFileManager defaultManager] directoryContentsAtPath:from]; NSEnumerator *en = [directoryContents objectEnumerator]; NSString *object; while ((object = [en nextObject])) { NSString *fromPath = [from stringByAppendingPathComponent:object]; NSString *toPath = [to stringByAppendingPathComponent:object]; BOOL isDirectory; if ([[NSFileManager defaultManager] fileExistsAtPath:fromPath isDirectory:&isDirectory]) { if (isDirectory) { [self copyFolderAtPath:fromPath toPath:toPath]; } else { [self copyFileAtPath:fromPath toPath:toPath]; } } } } @end