source: trunk/Update Checker Sources/UpdateCheckerAppDelegate.m @ 1038

Revision 1038, 11.3 KB checked in by astrange, 6 years ago (diff)

Add LGPL license headers to all files (or MIT where appropriate)
Get rid of "All rights reserved" which is LGPL-incompatible.

I didn't touch the Apple sample code, but they should've taken it out too...

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