// // MGMController.m // Chromatic // // Created by Mr. Gecko on 6/9/11. // Copyright (c) 2011 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ // // Permission to use, copy, modify, and/or distribute this software for any purpose // with or without fee is hereby granted, provided that the above copyright notice // and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, // OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, // DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS // ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // #import "MGMController.h" #import "MGMAddons.h" #import #import #import NSString * const MGMCopyright = @"Copyright (c) 2011 Mr. Gecko's Media (James Coleman). All rights reserved. http://mrgeckosmedia.com/"; NSString * const MGMVersion = @"MGMVersion"; NSString * const MGMLaunchCount = @"MGMLaunchCount"; NSString * const MGMChannel = @"MGMChannel"; NSString * const MGMChromiumPath = @"MGMChromiumPath"; NSString * const MGMCustomSnapshotURL = @"MGMCustomSnapshotURL"; NSString * const MGMMoveToTrash = @"MGMMoveToTrash"; NSString * const MGMDoneSound = @"MGMDoneSound"; NSString * const MGMLaunchWhenDone = @"MGMLaunchWhenDone"; NSString * const MGMQuitAfterLaunch = @"MGMQuitAfterLaunch"; NSString * const MGMCPApplications = @"/Applications"; NSString * const MGMCPUserApplications = @"~/Applications"; NSString * const MGMCPChromium = @"Chromium.app"; NSString * const MGMChromiumZip = @"chrome-mac.zip"; NSString * const MGMTMPPath = @"/tmp"; NSString * const MGMChannelsURL = @"https://omahaproxy.appspot.com/all.json?os=mac"; static NSString *MGMSnapshotURL = @"https://commondatastorage.googleapis.com/chromium-browser-snapshots/"; NSString * const MGMSnapshotPrefix = @"Mac/"; NSString * const MGMSVNLogsURL = @"http://build.chromium.org/f/chromium/perf/dashboard/ui/changelog.html?url=/trunk/src&range=%@:%@&mode=html&os=mac"; NSString * const MGMCChannel = @"channel"; NSString * const MGMCRevision = @"branch_base_position"; NSString * const MGMCStable = @"stable"; NSString * const MGMCBeta = @"beta"; NSString * const MGMCDev = @"dev"; NSString * const MGMCCanary = @"canary"; NSString * const MGMUBUpdate = @"Update"; NSString * const MGMUBInstall = @"Install"; NSString * const MGMUBCancel = @"Cancel"; @interface NSString (MGMAddonsSort) - (NSComparisonResult)numberCompare:(id)theItem; @end @implementation NSString (MGMAddonsSort) - (NSComparisonResult)numberCompare:(id)theItem { unsigned int theNumber = 0; unsigned int number = 0; if ([theItem isKindOfClass:[NSString class]]) sscanf([theItem UTF8String], "%u", &theNumber); sscanf([self UTF8String], "%u", &number); if (numbertheNumber) return NSOrderedDescending; return NSOrderedSame; } @end @interface NSOpenPanel (MGMIgnore) - (void)setDirectoryURL:(NSURL *)url; - (void)setDirectory:(NSString *)path; @end @implementation MGMController - (void)awakeFromNib { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setup) name:MGMGRDoneNotification object:nil]; [MGMReporter sharedReporter]; } - (void)setup { connectionManager = [[MGMURLConnectionManager manager] retain]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if ([defaults objectForKey:MGMVersion]==nil) { NSString *oldPathKey = @"cPathKey"; [defaults setObject:[defaults stringForKey:oldPathKey] forKey:MGMChromiumPath]; [defaults removeObjectForKey:oldPathKey]; [defaults setObject:[[MGMSystemInfo info] applicationVersion] forKey:MGMVersion]; } [self registerDefaults]; if ([defaults integerForKey:MGMLaunchCount]!=5) { [defaults setInteger:[defaults integerForKey:MGMLaunchCount]+1 forKey:MGMLaunchCount]; if ([defaults integerForKey:MGMLaunchCount]==5) { NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Donations"]; [alert setInformativeText:@"Thank you for using Chromatic. Chromatic is donation supported software. If you like using it, please consider giving a donation to help with development."]; [alert addButtonWithTitle:@"Yes"]; [alert addButtonWithTitle:@"No"]; int result = [alert runModal]; if (result==1000) [self donate:self]; } } if ([defaults objectForKey:MGMCustomSnapshotURL]!=nil && ![[defaults objectForKey:MGMCustomSnapshotURL] isEqual:@""]) MGMSnapshotURL = [defaults objectForKey:MGMCustomSnapshotURL]; about = [MGMAbout new]; preferences = [MGMPreferences new]; [preferences addPreferencesPaneClassName:@"MGMGeneralPane"]; [preferences addPreferencesPaneClassName:@"MGMAdvancedPane"]; startingUp = YES; [mainWindow makeKeyAndOrderFront:self]; [progress startAnimation:self]; [updateButton setEnabled:NO]; [channelPopUp selectItemAtIndex:[defaults integerForKey:MGMChannel]]; channelRevisions = [NSMutableDictionary new]; NSString *path = [defaults objectForKey:MGMChromiumPath]; if (path==nil) path = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"Chromium"]; if (path==nil) { NSFileManager *manager = [NSFileManager defaultManager]; if ([manager isWritableFileAtPath:MGMCPApplications]) { path = [MGMCPApplications stringByAppendingPathComponent:MGMCPChromium]; } else { if ([manager fileExistsAtPath:[MGMCPUserApplications stringByExpandingTildeInPath]]) [manager createDirectoryAtPath:[MGMCPUserApplications stringByExpandingTildeInPath] withAttributes:nil]; path = [[MGMCPUserApplications stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMCPChromium]; } } chromiumPath = [path copy]; [self updateChromiumPath]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:MGMChannelsURL]] delegate:self]; [handler setFailWithError:@selector(channels:didFailWithError:)]; [handler setFinish:@selector(channelsFinished:)]; [connectionManager addHandler:handler]; revisionsArray = [NSMutableArray new]; NSString *url = [MGMSnapshotURL stringByAppendingFormat:@"?delimiter=/&prefix=%@", MGMSnapshotPrefix]; handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]] delegate:self]; [handler setFailWithError:@selector(revisions:didFailWithError:)]; [handler setFinish:@selector(revisionsFinished:)]; [statusField setStringValue:@"Loading revisions..."]; [connectionManager addHandler:handler]; } - (void)registerDefaults { NSMutableDictionary *defaults = [NSMutableDictionary dictionary]; [defaults setObject:[NSNumber numberWithInt:1] forKey:MGMLaunchCount]; [defaults setObject:[NSNumber numberWithInt:4] forKey:MGMChannel]; [defaults setObject:[NSNumber numberWithBool:YES] forKey:MGMMoveToTrash]; [defaults setObject:[NSNumber numberWithBool:YES] forKey:MGMDoneSound]; [defaults setObject:[NSNumber numberWithBool:NO] forKey:MGMLaunchWhenDone]; [defaults setObject:[NSNumber numberWithBool:NO] forKey:MGMQuitAfterLaunch]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; } - (IBAction)about:(id)sender { [about show]; } - (IBAction)preferences:(id)sender { [preferences showPreferences]; } - (IBAction)donate:(id)sender { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9184741"]]; } - (IBAction)openSource:(id)sender { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://opensource.mrgeckosmedia.com/Chromatic?application"]]; } - (void)channels:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { NSLog(@"%@", theError); NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Error loading channel info"]; [alert setInformativeText:[theError localizedDescription]]; [alert runModal]; } - (void)channelsFinished:(MGMURLBasicHandler *)theHandler { NSArray *versions = [[[[theHandler data] parseJSON] objectAtIndex:0] objectForKey:@"versions"]; if (versions==nil) { NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Error loading channel info"]; [alert setInformativeText:@"The JSON was unable to be parsed."]; [alert runModal]; } for (int i=0; i<[versions count]; i++) { NSDictionary *versionInfo = [versions objectAtIndex:i]; id channel = [versionInfo objectForKey:MGMCRevision]; [channelRevisions setObject:([channel isKindOfClass:[NSString class]] ? channel : [channel stringValue]) forKey:[versionInfo objectForKey:MGMCChannel]]; } [progress setIndeterminate:NO]; [progress startAnimation:self]; [progress setDoubleValue:0.1]; } - (void)revisions:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { NSLog(@"%@", theError); NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Error loading revisions"]; [alert setInformativeText:[theError localizedDescription]]; [alert runModal]; NSMenu *items = [NSMenu new]; [revisionsArray sortUsingSelector:@selector(numberCompare:)]; for (unsigned int i=0; i<[revisionsArray count]; i++) { NSMenuItem *item = [NSMenuItem new]; [item setTitle:[revisionsArray objectAtIndex:i]]; [items addItem:item]; [item release]; } [buildPopUp removeAllItems]; [buildPopUp setMenu:items]; [items release]; [revisionsArray release]; revisionsArray = nil; [self channelSelect:self]; } - (void)revisionsFinished:(MGMURLBasicHandler *)theHandler { NSError *error = nil; NSXMLDocument *xml = [[NSXMLDocument alloc] initWithData:[theHandler data] options:NSXMLDocumentTidyXML error:&error]; if ([progress doubleValue] < 0.9) { [progress incrementBy:0.02]; } if (error!=nil) { NSLog(@"%@", error); NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Error parsing revisions"]; [alert setInformativeText:[error localizedDescription]]; [alert runModal]; NSMenu *items = [NSMenu new]; [revisionsArray sortUsingSelector:@selector(numberCompare:)]; for (unsigned int i=0; i<[revisionsArray count]; i++) { NSMenuItem *item = [NSMenuItem new]; [item setTitle:[revisionsArray objectAtIndex:i]]; [items addItem:item]; [item release]; } [buildPopUp removeAllItems]; [buildPopUp setMenu:items]; [items release]; [revisionsArray release]; revisionsArray = nil; [self channelSelect:self]; } else { NSXMLElement *rootElement = [xml rootElement]; NSArray *isTruncated = [rootElement elementsForName:@"IsTruncated"]; NSArray *commonPrefixes = [rootElement elementsForName:@"CommonPrefixes"]; for (int i=0; i<[commonPrefixes count]; i++) { NSArray *prefix = [[commonPrefixes objectAtIndex:i] elementsForName:@"Prefix"]; if ([prefix count]<1) continue; NSArray *parsed = [[[prefix objectAtIndex:0] stringValue] componentsSeparatedByString:@"/"]; if ([parsed count]<2) continue; if ([[parsed objectAtIndex:1] length]>=40) continue; [revisionsArray addObject:[parsed objectAtIndex:1]]; } NSArray *nextMarkers = [rootElement elementsForName:@"NextMarker"]; if ([isTruncated count]>0 && [[[isTruncated objectAtIndex:0] stringValue] isEqual:@"true"] && [nextMarkers count]>0) { NSString *nextMarker = [[nextMarkers objectAtIndex:0] stringValue]; NSString *url = [MGMSnapshotURL stringByAppendingFormat:@"?delimiter=/&prefix=%@&marker=%@", MGMSnapshotPrefix, nextMarker]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]] delegate:self]; [handler setFailWithError:@selector(revisions:didFailWithError:)]; [handler setFinish:@selector(revisionsFinished:)]; [connectionManager addHandler:handler]; } else { NSMenu *items = [NSMenu new]; [revisionsArray sortUsingSelector:@selector(numberCompare:)]; for (unsigned int i=0; i<[revisionsArray count]; i++) { NSMenuItem *item = [NSMenuItem new]; [item setTitle:[revisionsArray objectAtIndex:i]]; [items addItem:item]; [item release]; } [buildPopUp removeAllItems]; [buildPopUp setMenu:items]; [items release]; [revisionsArray release]; revisionsArray = nil; [statusField setStringValue:@""]; [self channelSelect:self]; } } [xml release]; } - (IBAction)channelSelect:(id)sender { [[NSUserDefaults standardUserDefaults] setInteger:[channelPopUp indexOfSelectedItem] forKey:MGMChannel]; NSArray *revisions = [buildPopUp itemTitles]; NSString *revision = nil; if ([channelPopUp indexOfSelectedItem]==0) revision = [channelRevisions objectForKey:MGMCStable]; else if ([channelPopUp indexOfSelectedItem]==1) revision = [channelRevisions objectForKey:MGMCBeta]; else if ([channelPopUp indexOfSelectedItem]==2) revision = [channelRevisions objectForKey:MGMCDev]; else if ([channelPopUp indexOfSelectedItem]==3) revision = [channelRevisions objectForKey:MGMCCanary]; if (revision==nil) revision = [revisions lastObject]; long itemIndex = [revisions indexOfObject:revision]; if (itemIndex==NSNotFound) { [buildWarningField setHidden:NO]; for (unsigned int i=0; i<[revisions count]; i++) { if ([(NSString *)[revisions objectAtIndex:i] numberCompare:revision]==NSOrderedDescending) { itemIndex = i; break; } } if (itemIndex==NSNotFound) itemIndex = [revisions count]-1; } else { [buildWarningField setHidden:YES]; } [buildPopUp selectItemAtIndex:itemIndex]; if (startingUp) [progress setDoubleValue:0.9]; [self buildSelect:self]; } - (IBAction)buildSelect:(id)sender { NSURL *buildURL = [[[[NSURL URLWithString:MGMSnapshotURL] appendPathComponent:MGMSnapshotPrefix] appendPathComponent:[buildPopUp titleOfSelectedItem]] appendPathComponent:@"REVISIONS"]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:buildURL] delegate:self]; [handler setFailWithError:@selector(revision:didFailWithError:)]; [handler setReceiveResponse:@selector(revision:didReceiveResponse:)]; [handler setFinish:@selector(revisionDidFinish:)]; [connectionManager addHandler:handler]; if (![[yourBuildField stringValue] isEqual:@""] && [buildPopUp indexOfSelectedItem]!=-1 && ![[yourBuildField stringValue] isEqual:[buildPopUp titleOfSelectedItem]]) [svnLogsButton setEnabled:YES]; else [svnLogsButton setEnabled:NO]; } - (void)revision:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { NSLog(@"%@", theError); NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Error loading revision info"]; [alert setInformativeText:[theError localizedDescription]]; [alert runModal]; if (startingUp) { [progress setDoubleValue:1.0]; [progress setIndeterminate:YES]; [progress setDoubleValue:0.0]; [progress stopAnimation:self]; [updateButton setEnabled:YES]; startingUp = NO; } } - (void)revision:(MGMURLBasicHandler *)theHandler didReceiveResponse:(NSHTTPURLResponse *)theResponse { NSString *modified = [[theResponse allHeaderFields] objectForKey:@"Last-Modified"]; NSDateFormatter *formatter = [NSDateFormatter new]; [formatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]]; [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"]; NSDate *date = [formatter dateFromString:modified]; [formatter release]; formatter = [NSDateFormatter new]; [formatter setDateFormat:@"MMMM d, yyyy hh:mm:ss a"]; NSString *dateString = [formatter stringFromDate:date]; [formatter release]; if (dateString!=nil) [buildDateField setStringValue:dateString]; else [buildDateField setStringValue:@"N/A"]; } - (void)revisionDidFinish:(MGMURLBasicHandler *)theHandler { NSDictionary *revisionInfo = [[theHandler data] parseJSON]; NSString *webkit = [NSString stringWithFormat:@"%@", [revisionInfo objectForKey:@"webkit_revision"]]; if (webkit!=nil) [webKitBuildField setStringValue:webkit]; else [webKitBuildField setStringValue:@"0"]; NSString *v8 = [NSString stringWithFormat:@"%@", [revisionInfo objectForKey:@"v8_revision"]]; if (v8!=nil) [v8BuildField setStringValue:v8]; else [v8BuildField setStringValue:@"0"]; if (startingUp) { [progress setDoubleValue:1.0]; [progress setIndeterminate:YES]; [progress setDoubleValue:0.0]; [progress stopAnimation:self]; [updateButton setEnabled:YES]; startingUp = NO; } } - (IBAction)choosePath:(id)sender { NSOpenPanel *openPanel = [NSOpenPanel openPanel]; if ([openPanel respondsToSelector:@selector(setDirectory:)]) [openPanel setDirectory:[chromiumPath stringByDeletingLastPathComponent]]; else [openPanel setDirectoryURL:[NSURL fileURLWithPath:[chromiumPath stringByDeletingLastPathComponent]]]; [openPanel setCanChooseFiles:NO]; [openPanel setCanChooseDirectories:YES]; [openPanel setAllowsMultipleSelection:NO]; [openPanel setTitle:@"Choose path for Chromium"]; [openPanel setPrompt:@"Choose"]; int result = [openPanel runModal]; if (result==NSOKButton) { NSString *newPath = [[[openPanel URLs] objectAtIndex:0] path]; NSFileManager *manager = [NSFileManager defaultManager]; if ([manager isWritableFileAtPath:newPath]) { [chromiumPath release]; chromiumPath = [[newPath stringByAppendingPathComponent:MGMCPChromium] retain]; [[NSUserDefaults standardUserDefaults] setObject:chromiumPath forKey:MGMChromiumPath]; [self updateChromiumPath]; } else { NSAlert *alert = [[NSAlert new] autorelease]; [alert setInformativeText:@"Not Writable"]; [alert setMessageText:@"The directory you choose is not writable. To beable to use it with Chromatic, you need to change permissions."]; [alert runModal]; } } } - (void)updateChromiumPath { NSFileManager *manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:chromiumPath]) { NSDictionary *chromiumInfo = (NSDictionary *)CFBundleCopyInfoDictionaryInDirectory((CFURLRef)[NSURL fileURLWithPath:chromiumPath]); NSString *build = [chromiumInfo objectForKey:@"SVNRevision"]; if (build==nil) build = [chromiumInfo objectForKey:@"SCMRevision"]; if (build!=nil) [yourBuildField setStringValue:build]; [chromiumInfo release]; NSDictionary *chromeAttributes = [manager attributesOfItemAtPath:chromiumPath]; NSDate *date = [chromeAttributes objectForKey:NSFileModificationDate]; NSDateFormatter *formatter = [[NSDateFormatter new] autorelease]; [formatter setDateFormat:@"MMMM d, yyyy hh:mm:ss a"]; [installDateField setStringValue:[formatter stringFromDate:date]]; [launchButton setEnabled:YES]; [updateButton setTitle:MGMUBUpdate]; if (![[yourBuildField stringValue] isEqual:@""] && [buildPopUp indexOfSelectedItem]!=-1 && ![[yourBuildField stringValue] isEqual:[buildPopUp titleOfSelectedItem]]) [svnLogsButton setEnabled:YES]; else [svnLogsButton setEnabled:NO]; } else { [launchButton setEnabled:NO]; [svnLogsButton setEnabled:NO]; [yourBuildField setStringValue:@""]; [installDateField setStringValue:@""]; [updateButton setTitle:MGMUBInstall]; } [pathField setStringValue:[chromiumPath stringByDeletingLastPathComponent]]; } - (IBAction)launchChromium:(id)sender { [[NSWorkspace sharedWorkspace] launchApplication:chromiumPath]; if ([[NSUserDefaults standardUserDefaults] boolForKey:MGMQuitAfterLaunch]) [[NSApplication sharedApplication] terminate:self]; } - (IBAction)viewSVNLogs:(id)sender { NSString *revision1, *revision2, *tmp; revision1 = [yourBuildField stringValue]; revision2 = [buildPopUp titleOfSelectedItem]; if ([revision1 numberCompare:revision2]==NSOrderedDescending) { tmp = revision1; revision1 = revision2; revision2 = tmp; } NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:MGMSVNLogsURL, revision1, revision2]]; [[svnLogsBrowser mainFrame] loadRequest:[NSURLRequest requestWithURL:url]]; [svnLogsWindow makeKeyAndOrderFront:self]; } - (IBAction)update:(id)sender { if ([[updateButton title] isEqual:MGMUBCancel]) { [connectionManager cancelHandler:updateHandler]; [updateHandler release]; updateHandler = nil; [[NSFileManager defaultManager] removeItemAtPath:[MGMTMPPath stringByAppendingPathComponent:MGMChromiumZip]]; [self updateDone]; } else { [progress startAnimation:self]; startTime = [[NSDate date] timeIntervalSince1970]; NSURL *url = [[[[NSURL URLWithString:MGMSnapshotURL] appendPathComponent:MGMSnapshotPrefix] appendPathComponent:[buildPopUp titleOfSelectedItem]] appendPathComponent:MGMChromiumZip]; [updateHandler release]; updateHandler = [[MGMURLBasicHandler handlerWithRequest:[NSURLRequest requestWithURL:url] delegate:self] retain]; [updateHandler setFile:[MGMTMPPath stringByAppendingPathComponent:MGMChromiumZip]]; [updateHandler setFailWithError:@selector(update:didFailWithError:)]; [updateHandler setReceiveResponse:@selector(update:didReceiveResponse:)]; [updateHandler setBytesReceived:@selector(update:receivedBytes:totalBytes:expectedBytes:)]; [updateHandler setFinish:@selector(updateDidFinish:)]; [updateHandler setSynchronous:NO]; [connectionManager addHandler:updateHandler]; [channelPopUp setEnabled:NO]; [buildPopUp setEnabled:NO]; [updateButton setTitle:MGMUBCancel]; } } - (void)windowWillClose:(NSNotification *)notification { [[svnLogsBrowser mainFrame] loadHTMLString:@"" baseURL:nil]; } - (void)update:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { NSLog(@"%@", theError); NSAlert *alert = [[NSAlert new] autorelease]; [alert setMessageText:@"Error downloading update"]; [alert setInformativeText:[theError localizedDescription]]; [alert runModal]; } - (void)update:(MGMURLBasicHandler *)theHandler didReceiveResponse:(NSHTTPURLResponse *)theResponse { [progress setIndeterminate:NO]; [progress startAnimation:self]; bytesReceivedArray = [NSMutableArray new]; } - (void)update:(MGMURLBasicHandler *)theHandler receivedBytes:(unsigned long)theBytes totalBytes:(unsigned long)theTotalBytes expectedBytes:(unsigned long)theExpectedBytes { bytesReceived += theBytes; if (lastCheck==nil || [lastCheck timeIntervalSinceNow]<-1.0) { [lastCheck release]; lastCheck = [NSDate new]; while ([bytesReceivedArray count]>15) { [bytesReceivedArray removeObjectAtIndex:0]; } [bytesReceivedArray addObject:[NSNumber numberWithUnsignedLong:bytesReceived]]; averageBytesReceived = 0; for (int i=0; i<[bytesReceivedArray count]; i++) { averageBytesReceived += [[bytesReceivedArray objectAtIndex:i] unsignedLongValue]; } averageBytesReceived /= [bytesReceivedArray count]; [statusField setStringValue:[NSString stringWithFormat:@"%@ of %@ (%@/sec) - %@", [NSString readableMemory:theTotalBytes], [NSString readableMemory:theExpectedBytes], [NSString readableMemory:bytesReceived], [NSString readableTime:(theExpectedBytes-theTotalBytes)/averageBytesReceived]]]; bytesReceived = 0; } [progress setDoubleValue:(double)theTotalBytes/(double)theExpectedBytes]; totalBytes = theTotalBytes; } - (void)updateDidFinish:(MGMURLBasicHandler *)theHandler { [updateHandler release]; updateHandler = nil; [bytesReceivedArray release]; bytesReceivedArray = nil; [updateButton setEnabled:NO]; [progress setIndeterminate:YES]; [progress startAnimation:self]; [statusField setStringValue:@"Uncompressing and installing update..."]; unzipTask = [NSTask new]; [unzipTask setLaunchPath:@"/usr/bin/ditto"]; [unzipTask setArguments:[NSArray arrayWithObjects:@"-v", @"-x", @"-k", @"--rsrc", [MGMTMPPath stringByAppendingPathComponent:MGMChromiumZip], MGMTMPPath, nil]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateUnzipped:) name:NSTaskDidTerminateNotification object:unzipTask]; [unzipTask launch]; } - (void)updateUnzipped:(NSNotification *)theNotification { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSFileManager *manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:chromiumPath]) { if ([defaults boolForKey:MGMMoveToTrash]) { NSString *trash = [@"~/.Trash" stringByExpandingTildeInPath]; NSInteger tag; [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[chromiumPath stringByDeletingLastPathComponent] destination:trash files:[NSArray arrayWithObject:[chromiumPath lastPathComponent]] tag:&tag]; if (tag!=0) NSLog(@"Error Deleting: %ld", (long)tag); } else { [manager removeItemAtPath:chromiumPath]; } } NSString *extractedPath = [[MGMTMPPath stringByAppendingPathComponent:[MGMChromiumZip stringByDeletingPathExtension]] stringByAppendingPathComponent:MGMCPChromium]; if (![manager moveItemAtPath:extractedPath toPath:chromiumPath]) { NSBeep(); [self updateDone]; [statusField setStringValue:[NSString stringWithFormat:@"Unable to %@ due to an error. This may be a permissions issue.", [[updateButton title] lowercaseString]]]; return; } [manager removeItemAtPath:[extractedPath stringByDeletingLastPathComponent]]; [manager removeItemAtPath:[MGMTMPPath stringByAppendingPathComponent:MGMChromiumZip]]; [manager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:chromiumPath]; [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; [self updateDone]; int time = [[NSDate date] timeIntervalSince1970]-startTime; [statusField setStringValue:[NSString stringWithFormat:@"%@ downloaded at %@/sec in %@", [NSString readableMemory:totalBytes], [NSString readableMemory:averageBytesReceived], [NSString readableTime:time]]]; if ([defaults boolForKey:MGMDoneSound]) { NSSound *done = [NSSound soundNamed:@"Glass"]; [done setDelegate:self]; [done play]; } [updateButton setEnabled:YES]; if ([defaults boolForKey:MGMLaunchWhenDone] && [defaults boolForKey:MGMDoneSound] && ![defaults boolForKey:MGMQuitAfterLaunch]) [self launchChromium:self]; } - (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)didFinish { if (didFinish) { [sound release]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:MGMLaunchWhenDone] && [defaults boolForKey:MGMQuitAfterLaunch]) [self launchChromium:self]; } } - (void)updateDone { [self updateChromiumPath]; [channelPopUp setEnabled:YES]; [buildPopUp setEnabled:YES]; [progress setIndeterminate:YES]; [progress setDoubleValue:0.0]; [progress stopAnimation:self]; [statusField setStringValue:@""]; } - (void)applicationWillTerminate:(NSNotification *)aNotification { if (updateHandler!=nil) [self update:self]; } @end