root/branches/perian-1.1/Update Checker Sources/UpdateCheckerAppDelegate.m

Revision 968, 10.7 kB (checked in by astrange, 3 months ago)

Make disabling automatic updates actually work.
Update Checker: Don't overwrite NextRunDate? if it's set to distantFuture.
PrefPane?: Revert r473

Line 
1 //
2 //  UpdateCheckerAppDelegate.m
3 //  Perian
4 //
5 //  Created by Augie Fackler on 1/6/07.
6 //  Copyright 2007 __MyCompanyName__. All rights reserved.
7 //
8 // This is really just a heavily-customized version of SUUpdate designed
9 // so that the updates are done using an app and not a framework. We do
10 // this so that things like little snitch don't give us problems, and so
11 // we can be more sure that the update won't get run twice accidentaly.
12
13 #import "UpdateCheckerAppDelegate.h"
14 #include <stdlib.h>
15
16 //define the following to use the beta appcast URL, but DON'T commit that change
17 //#define betaAppcastUrl @"whatever"
18
19 @interface UpdateCheckerAppDelegate (private)
20 - (void)showUpdateErrorAlertWithInfo:(NSString *)info;
21 @end
22
23 @implementation UpdateCheckerAppDelegate
24
25 - (void)dealloc
26 {
27     [downloader release];
28     [downloadPath release];
29     [lastRunDate release];
30     [super dealloc];
31 }
32
33 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
34 {
35         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
36         lastRunDate = [[defaults objectForKey:NEXT_RUN_KEY] retain];
37    
38     if (![lastRunDate isEqualToDate:[NSDate distantFuture]]) {
39         [defaults setObject:[NSDate dateWithTimeIntervalSinceNow:TIME_INTERVAL_TIL_NEXT_RUN] forKey:NEXT_RUN_KEY];
40     }
41    
42         manualRun = [defaults boolForKey:MANUAL_RUN_KEY];
43         [defaults removeObjectForKey:MANUAL_RUN_KEY];
44         [defaults synchronize];
45         [self doUpdateCheck];
46 }
47
48 - (void)doUpdateCheck
49 {
50         NSString *updateUrlString = SUInfoValueForKey(UPDATE_URL_KEY);
51        
52         if (!updateUrlString) { [NSException raise:@"NoFeedURL" format:@"No feed URL is specified in the Info.plist!"]; }
53
54 #ifdef betaAppcastUrl
55         updateUrlString = [[updateUrlString substringToIndex:[updateUrlString length]-4] stringByAppendingFormat:@"-%@.xml", betaAppcastUrl];
56 #endif
57         if(manualRun)
58                 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:UPDATE_STATUS_NOTIFICATION object:@"Starting"];
59        
60         SUAppcast *appcast = [[SUAppcast alloc] init];
61         [appcast setDelegate:self];
62         [appcast fetchAppcastFromURL:[NSURL URLWithString:updateUrlString]];
63 }
64
65 - (void)appcastDidFinishLoading:(SUAppcast *)appcast
66 {
67         latest = [[appcast newestItem] retain];
68        
69         if (![latest fileVersion])
70         {
71         [self updateFailed];
72                 [NSException raise:@"SUAppcastException" format:@"Can't extract a version string from the appcast feed. The filenames should look like YourApp_1.5.tgz, where 1.5 is the version number."];
73         }
74        
75         // OS version (Apple recommends using SystemVersion.plist instead of Gestalt() here, don't ask me why).
76         // This code *should* use NSSearchPathForDirectoriesInDomains(NSCoreServiceDirectory, NSSystemDomainMask, YES)
77         // but that returns /Library/CoreServices for some reason
78         NSString *versionPlistPath = @"/System/Library/CoreServices/SystemVersion.plist";
79         NSString *currentSystemVersion = [[[NSDictionary dictionaryWithContentsOfFile:versionPlistPath] objectForKey:@"ProductVersion"] retain];
80
81         BOOL updateAvailable = SUStandardVersionComparison([latest minimumSystemVersion], currentSystemVersion);
82     NSString *panePath = [[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
83         updateAvailable = (updateAvailable && (SUStandardVersionComparison([latest fileVersion], [[NSBundle bundleWithPath:panePath] objectForInfoDictionaryKey:@"CFBundleVersion"]) == NSOrderedAscending));
84        
85         if (![[panePath lastPathComponent] isEqualToString:@"Perian.prefPane"]) {
86                 NSLog(@"The update checker needs to be run from inside the preference pane, quitting...");
87                 updateAvailable = 0;
88         }
89        
90         NSString *skippedVersion = [[NSUserDefaults standardUserDefaults] objectForKey:SKIPPED_VERSION_KEY];
91         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
92         if (updateAvailable && (!skippedVersion ||
93                 (skippedVersion && ![skippedVersion isEqualToString:[latest versionString]]))) {
94                 if(manualRun)
95                         [[NSDistributedNotificationCenter defaultCenter] postNotificationName:UPDATE_STATUS_NOTIFICATION object:@"YesUpdates"];
96                 [self showUpdatePanelForItem:latest];
97         } else {
98                 if(manualRun)
99                         [[NSDistributedNotificationCenter defaultCenter] postNotificationName:UPDATE_STATUS_NOTIFICATION object:@"NoUpdates"];
100                 [[NSApplication sharedApplication] terminate:self];
101         }
102    
103     [appcast release];
104 }
105
106 - (void)appcastDidFailToLoad:(SUAppcast *)appcast
107 {
108         [self updateFailed];
109         if(manualRun)
110                 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:UPDATE_STATUS_NOTIFICATION object:@"Error"];
111     [appcast release];
112         [[NSApplication sharedApplication] terminate:self];     
113 }
114
115 - (void)showUpdatePanelForItem:(SUAppcastItem *)updateItem
116 {
117         updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem];
118         [updateAlert setDelegate:self];
119         [updateAlert showWindow:self];
120 }
121
122 - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoice)choice
123 {
124         if (choice == SUInstallUpdateChoice) {
125                 [self beginDownload];
126         } else {
127                 if (choice == SUSkipThisVersionChoice)
128                         [[NSUserDefaults standardUserDefaults] setObject:[latest versionString] forKey:SKIPPED_VERSION_KEY];
129                 [[NSApplication sharedApplication] terminate:self];
130         }
131 }
132
133 - (void)showUpdateErrorAlertWithInfo:(NSString *)info
134 {
135         NSRunAlertPanel(SULocalizedString(@"Update Error!", nil), info, SULocalizedString(@"Cancel", nil), nil, nil);
136 }
137
138 - (void)beginDownload
139 {
140         statusController = [[SUStatusController alloc] init];
141         [statusController beginActionWithTitle:SULocalizedString(@"Downloading update...", nil) maxProgressValue:0 statusText:nil];
142         [statusController setButtonTitle:SULocalizedString(@"Cancel", nil) target:self action:@selector(cancelDownload:) isDefault:NO];
143         [statusController showWindow:self];
144        
145         downloader = [[NSURLDownload alloc] initWithRequest:[NSURLRequest requestWithURL:[latest fileURL]] delegate:self];     
146 }
147
148 - (void)cancelDownload:(id)sender
149 {
150         [downloader cancel];
151         [statusController close];
152         [[NSApplication sharedApplication] terminate:self];
153 }
154
155 #pragma mark NSURLDownload delegate methods
156 - (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
157 {
158         [statusController setMaxProgressValue:[response expectedContentLength]];
159 }
160
161 - (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)name
162 {
163         // If name ends in .txt, the server probably has a stupid MIME configuration. We'll give
164         // the developer the benefit of the doubt and chop that off.
165         if ([[name pathExtension] isEqualToString:@"txt"])
166                 name = [name stringByDeletingPathExtension];
167        
168         // We create a temporary directory in /tmp and stick the file there.
169         NSString *tempDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
170         BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:tempDir attributes:nil];
171         if (!success)
172         {
173                 [NSException raise:@"SUFailTmpWrite" format:@"Couldn't create temporary directory in /tmp"];
174                 [download cancel];
175                 [download release];
176         }
177        
178         downloadPath = [[tempDir stringByAppendingPathComponent:name] retain];
179         [download setDestination:downloadPath allowOverwrite:YES];
180 }
181
182 - (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length
183 {
184         [statusController setProgressValue:[statusController progressValue] + length];
185         [statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%.0lfk of %.0lfk", nil), [statusController progressValue] / 1024.0, [statusController maxProgressValue] / 1024.0]];
186 }
187
188 - (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
189 {
190         [self updateFailed];
191         NSLog(@"Download error: %@", [error localizedDescription]);
192         [self showUpdateErrorAlertWithInfo:SULocalizedString(@"An error occurred while trying to download the newest version of Perian. Please try again later.", nil)];
193         [[NSApplication sharedApplication] terminate:self];
194 }
195
196 //Stolen from sprakle
197 - (BOOL)extractDMG:(NSString *)archivePath
198 {
199         // First, we internet-enable the volume.
200         NSTask *hdiTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/env" arguments:[NSArray arrayWithObjects:@"hdiutil", @"internet-enable", @"-quiet", archivePath, nil]];
201         [hdiTask waitUntilExit];
202         if ([hdiTask terminationStatus] != 0) { return NO; }
203        
204         // Now, open the volume; it'll extract into its own directory.
205         hdiTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/env" arguments:[NSArray arrayWithObjects:@"hdiutil", @"attach", @"-idme", @"-noidmereveal", @"-noidmetrash", @"-noverify", @"-nobrowse", @"-noautoopen", @"-quiet", archivePath, nil]];
206         [hdiTask waitUntilExit];
207         if ([hdiTask terminationStatus] != 0) { return NO; }
208        
209         return YES;
210 }
211
212 extern char **environ;
213
214 - (void)downloadDidFinish:(NSURLDownload *)download
215 {
216         [download release];
217         downloader = nil;
218        
219         //Indeterminate progress bar
220         [statusController setMaxProgressValue:0];
221         [statusController setStatusText:SULocalizedString(@"Extracting...", nil)];
222        
223         if(![self extractDMG:downloadPath])
224         {
225                 [self updateFailed];
226                 [self showUpdateErrorAlertWithInfo:NSLocalizedString(@"Could not Extract Downloaded File",@"")];
227         }
228        
229         NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:[downloadPath stringByDeletingLastPathComponent]];
230         NSString *file = nil;
231         NSString *prefpanelocation = nil;
232         while((file = [dirEnum nextObject]) != nil)
233         {
234                 if([[[dirEnum fileAttributes] objectForKey:NSFileTypeSymbolicLink] boolValue])
235                         [dirEnum skipDescendents];
236                 if([[file pathExtension] isEqualToString:@"prefPane"])
237                 {
238                         NSString *containingLocation = [downloadPath stringByDeletingLastPathComponent];
239                         NSString *oldLocation = [containingLocation stringByAppendingPathComponent:file];
240                         prefpanelocation = [[containingLocation stringByDeletingLastPathComponent] stringByAppendingPathComponent:[file lastPathComponent]];
241                         [[NSFileManager defaultManager] movePath:oldLocation toPath:prefpanelocation handler:nil];
242                 }
243         }
244        
245         char *buf = NULL;
246         asprintf(&buf, "open \"$PREFPANE_LOCATION\"; rm -rf \"$TEMP_FOLDER\"");
247         if(!buf)
248         {
249                 [self updateFailed];
250                 [self showUpdateErrorAlertWithInfo:NSLocalizedString(@"Could not Create Extraction Script",@"")];
251         }
252                
253         char *args[] = {"/bin/sh", "-c", buf, NULL};
254         setenv("PREFPANE_LOCATION", [prefpanelocation fileSystemRepresentation], 1);
255         setenv("TEMP_FOLDER", [[downloadPath stringByDeletingLastPathComponent] fileSystemRepresentation], 1);
256     int forkVal = fork();
257     if(forkVal == -1)
258         {
259                 [self updateFailed];
260                 [self showUpdateErrorAlertWithInfo:NSLocalizedString(@"Could not Run Update",@"")];
261         }
262         if(forkVal == 0)
263                 execve("/bin/sh", args, environ);
264         [NSApp terminate:self];
265         //And, we are out of here!!!
266 }       
267
268 - (BOOL)showsReleaseNotes
269 {
270         return YES;
271 }
272
273 - (void)updateFailed
274 {
275     if(lastRunDate == nil)
276         [[NSUserDefaults standardUserDefaults] removeObjectForKey:NEXT_RUN_KEY];
277     else
278         [[NSUserDefaults standardUserDefaults] setObject:lastRunDate forKey:NEXT_RUN_KEY];
279 }
280
281
282 @end
Note: See TracBrowser for help on using the browser.