Changeset 147

Show
Ignore:
Timestamp:
04/15/08 00:22:18 (1 month ago)
Author:
andym
Message:

Major refactoring, yay.
Moved out installation phase to separate classes, separating packages and flat-file installs.
Made the DMG cleanup less horrifying.
All-around prettification.
This may or may not break the world; please let me know.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/NSFileManager+Authentication.h

    r99 r147  
    1111 
    1212@interface NSFileManager (SUAuthenticationAdditions) 
    13 - (BOOL)copyPath:(NSString *)src overPath:(NSString *)dst withAuthentication:(BOOL)useAuthentication
     13- (BOOL)copyPathWithAuthentication:(NSString *)src overPath:(NSString *)dst error:(NSError **)error
    1414@end 
    1515 
  • trunk/NSFileManager+Authentication.m

    r145 r147  
    1919 
    2020#import "NSFileManager+ExtendedAttributes.h" 
    21  
    22 // TN OV02 says that the range between 1000 and 9999 can be used for 
    23 // application-defined errors.  errToolFailedError will indicate that an 
    24 // executed program crashed or indicated failure with a nonzero exit status 
    25 static const OSStatus errToolFailedError = 1000; 
    26  
    27 static OSStatus AuthorizationExecuteWithPrivilegesAndWait( 
    28                                                                                                                   AuthorizationRef authorization, 
    29                                                                                                                   const char* executablePath, 
    30                                                                                                                   AuthorizationFlags options, 
    31                                                                                                                   const char* const* arguments) { 
     21static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments) 
     22
    3223        sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL); 
    33         OSStatus ret; 
    34         ret = AuthorizationExecuteWithPrivileges(authorization, 
    35                                                  executablePath, 
    36                                                  options, 
    37                                                  (char* const*)arguments, 
    38                                                  NULL); 
    39         if (ret == errAuthorizationSuccess) { 
     24        BOOL returnValue = YES; 
     25 
     26        if (AuthorizationExecuteWithPrivileges(authorization, executablePath, options, (char* const*)arguments, NULL) == errAuthorizationSuccess) 
     27        { 
    4028                int status; 
    4129                pid_t pid = wait(&status); 
    42                 if (pid == -1 || 
    43                     !WIFEXITED(status) || WEXITSTATUS(status) != 0) { 
    44                         ret = errToolFailedError; 
    45                 } 
    46         } 
     30                if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) 
     31                        returnValue = NO; 
     32        } 
     33        else 
     34                returnValue = NO; 
     35                 
    4736        signal(SIGCHLD, oldSigChildHandler); 
    48         return ret
     37        return returnValue
    4938} 
    5039 
     
    8675} 
    8776 
    88 - (NSString *)_temporaryCopyNameForPath:(NSString *)filename 
    89 
    90         return [[[filename stringByDeletingPathExtension] stringByAppendingString:@".old"] stringByAppendingPathExtension:[filename pathExtension]]; 
    91 
    92  
    93 - (BOOL)_copyPathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst 
     77- (NSString *)_temporaryCopyNameForPath:(NSString *)path 
     78
     79        // Let's try to read the version number so the filename will be more meaningful. 
     80        NSString *postFix; 
     81        NSBundle *bundle; 
     82        if ((bundle = [NSBundle bundleWithPath:path])) 
     83        { 
     84                // We'll clean it up a little for safety. 
     85                NSMutableCharacterSet *validCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; 
     86                [validCharacters formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@".-()"]]; 
     87                postFix = [[bundle objectForInfoDictionaryKey:@"CFBundleVersion"] stringByTrimmingCharactersInSet:[validCharacters invertedSet]]; 
     88        } 
     89        else 
     90                postFix = @"old"; 
     91        return [[[path stringByDeletingPathExtension] stringByAppendingFormat:@" (%@)", postFix] stringByAppendingPathExtension:[path pathExtension]]; 
     92
     93 
     94- (BOOL)_copyPathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst error:(NSError **)error 
    9495{ 
    9596        NSString *tmp = [self _temporaryCopyNameForPath:dst]; 
     
    9899        const char* dstPath = [dst fileSystemRepresentation]; 
    99100         
    100         struct stat sb, dstSB; 
    101         if ((stat(srcPath, &sb) != 0) || 
    102             (stat(tmpPath, &sb) == 0) || 
    103             (stat(dstPath, &dstSB) != 0)) { 
    104                 return NO; 
    105         } 
     101        struct stat dstSB; 
     102        stat(dstPath, &dstSB); 
    106103         
    107104        AuthorizationRef auth = NULL; 
     
    123120                 
    124121                const char* executables[] = { 
     122                        "/bin/rm", 
    125123                        "/bin/mv", 
    126124                        "/bin/mv", 
     
    136134                // list. 
    137135                const char* const argumentLists[][4] = { 
     136                        { "-rf", tmpPath, NULL }, // make room for the temporary file... this is kinda unsafe; should probably do something better. 
    138137                        { "-f", dstPath, tmpPath, NULL },  // mv 
    139138                        { "-f", srcPath, dstPath, NULL },  // mv 
     
    147146                int commandIndex = 0; 
    148147                for (; executables[commandIndex] != NULL; ++commandIndex) { 
    149                         if (res) { 
    150                                 res = ( 
    151                                            AuthorizationExecuteWithPrivilegesAndWait(auth, 
    152                                                                                                                                  executables[commandIndex], 
    153                                                                                                                                  kAuthorizationFlagDefaults, 
    154                                                                                                                                  argumentLists[commandIndex]) == 
    155                                            errAuthorizationSuccess); 
    156                         } 
     148                        if (res) 
     149                                res = AuthorizationExecuteWithPrivilegesAndWait(auth, executables[commandIndex], kAuthorizationFlagDefaults, argumentLists[commandIndex]); 
    157150                } 
    158151                 
     
    178171                 
    179172                for (; executables[commandIndex] != NULL; ++commandIndex) { 
    180                         if (res) { 
    181                                 res = ( 
    182                                            AuthorizationExecuteWithPrivilegesAndWait(auth, 
    183                                                                                                                                  executables[commandIndex], 
    184                                                                                                                                  kAuthorizationFlagDefaults, 
    185                                                                                                                                  argumentLists[commandIndex]) == 
    186                                            errAuthorizationSuccess); 
    187                         } 
     173                        if (res) 
     174                                res = AuthorizationExecuteWithPrivilegesAndWait(auth, executables[commandIndex], kAuthorizationFlagDefaults, argumentLists[commandIndex]); 
    188175                } 
    189176                 
    190177                AuthorizationFree(auth, 0); 
     178                 
     179                if (!res) 
     180                { 
     181                        // Something went wrong somewhere along the way, but we're not sure exactly where. 
     182                        NSString *errorMessage = [NSString stringWithFormat:@"Authenticated file copy from %@ to %@ failed.", src, dst]; 
     183                        if (error != NULL) 
     184                                *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]]; 
     185                } 
     186        } 
     187        else 
     188        { 
     189                if (error != NULL) 
     190                        *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:@"Couldn't get permission to authenticate." forKey:NSLocalizedDescriptionKey]]; 
    191191        } 
    192192        return res; 
    193193} 
    194194 
    195 - (BOOL)copyPath:(NSString *)src overPath:(NSString *)dst withAuthentication:(BOOL)useAuthentication 
    196 
    197         if ([[NSFileManager defaultManager] isWritableFileAtPath:dst] && [[NSFileManager defaultManager] isWritableFileAtPath:[dst stringByDeletingLastPathComponent]]) 
    198         { 
    199                 NSInteger tag = 0; 
    200                 BOOL result; 
    201                 NSString *tmpPath = [self _temporaryCopyNameForPath:dst]; 
    202                 result = [[NSFileManager defaultManager] movePath:dst toPath:tmpPath handler:self]; 
    203                 result &= [[NSFileManager defaultManager] copyPath:src toPath:dst handler:nil]; 
    204                 result &= [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[tmpPath stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[tmpPath lastPathComponent]] tag:&tag]; 
    205                  
    206                 // If the currently-running application is trusted, the new 
    207                 // version should be trusted as well.  Remove it from the 
    208                 // quarantine to avoid a delay at launch, and to avoid 
    209                 // presenting the user with a confusing trust dialog. 
    210                 // 
    211                 // This needs to be done after the application is moved to its 
    212                 // new home in case it's moved across filesystems: if that 
    213                 // happens, the move is actually a copy, and it may result 
    214                 // in the application being quarantined. 
    215                 if (result) 
    216                         [self releaseFromQuarantine:dst]; 
    217                  
    218                 return result; 
    219         } 
    220         else if (useAuthentication == YES) 
    221                 return [self _copyPathWithForcedAuthentication:src toPath:dst]; 
    222         else 
     195- (BOOL)copyPathWithAuthentication:(NSString *)src overPath:(NSString *)dst error:(NSError **)error 
     196
     197        if (![[NSFileManager defaultManager] fileExistsAtPath:dst]) 
     198        { 
     199                NSString *errorMessage = [NSString stringWithFormat:@"Couldn't copy %@ over %@ because there is no file at %@.", src, dst, dst]; 
     200                if (error != NULL) 
     201                        *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]]; 
    223202                return NO; 
    224 
    225  
    226 - (BOOL)fileManager:(NSFileManager *)fileManager shouldProceedAfterError:(NSError *)error copyingItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath 
    227 
    228         NSLog(@"Sparkle: An error occurred in copying %@ to %@: %@", srcPath, dstPath, [error localizedDescription]); 
    229         return NO; 
    230 
    231  
    232 - (BOOL)fileManager:(NSFileManager *)fileManager shouldProceedAfterError:(NSError *)error movingItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath 
    233 
    234         NSLog(@"Sparkle: An error occurred in moving %@ to %@: %@", srcPath, dstPath, [error localizedDescription]); 
    235         return NO; 
     203        } 
     204 
     205        if (![[NSFileManager defaultManager] isWritableFileAtPath:dst] || ![[NSFileManager defaultManager] isWritableFileAtPath:[dst stringByDeletingLastPathComponent]]) 
     206                return [self _copyPathWithForcedAuthentication:src toPath:dst error:error]; 
     207 
     208        NSString *tmpPath = [self _temporaryCopyNameForPath:dst]; 
     209         
     210        // We get more error information if we're running on Leopard, so let's use that if we can. 
     211        if ([[NSFileManager defaultManager] respondsToSelector:@selector(moveItemAtPath:toPath:error:)]) 
     212        { 
     213                if (![[NSFileManager defaultManager] moveItemAtPath:dst toPath:tmpPath error:error]) { return NO; } 
     214                if (![[NSFileManager defaultManager] copyItemAtPath:src toPath:dst error:error]) { return NO; } 
     215        } 
     216        else // We just get generic error messages. 
     217        { 
     218                if (![[NSFileManager defaultManager] movePath:dst toPath:tmpPath handler:self]) 
     219                { 
     220                        if (error != NULL) 
     221                                *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't move %@ to %@.", dst, tmpPath] forKey:NSLocalizedDescriptionKey]]; 
     222                        return NO;                       
     223                } 
     224                if (![[NSFileManager defaultManager] copyPath:src toPath:dst handler:self]) 
     225                { 
     226                        if (error != NULL) 
     227                                *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't copy %@ to %@.", src, dst] forKey:NSLocalizedDescriptionKey]]; 
     228                        return NO;                       
     229                } 
     230        } 
     231         
     232        // Trash the old copy of the app. 
     233        NSInteger tag = 0; 
     234        if (![[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[tmpPath stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[tmpPath lastPathComponent]] tag:&tag]) 
     235        { 
     236                if (error != NULL) 
     237                        *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't move %@ to the trash. This is often a sign of a permissions error.", src, dst] forKey:NSLocalizedDescriptionKey]]; 
     238                return NO;                                       
     239        } 
     240         
     241        // If the currently-running application is trusted, the new 
     242        // version should be trusted as well.  Remove it from the 
     243        // quarantine to avoid a delay at launch, and to avoid 
     244        // presenting the user with a confusing trust dialog. 
     245        // 
     246        // This needs to be done after the application is moved to its 
     247        // new home in case it's moved across filesystems: if that 
     248        // happens, the move is actually a copy, and it may result 
     249        // in the application being quarantined. 
     250        [self releaseFromQuarantine:dst]; 
     251         
     252        return YES; 
    236253} 
    237254 
  • trunk/NTSynchronousTask.h

    r99 r147  
    44// 
    55//  Created by Steve Gehrman on 9/29/05. 
    6 //  Copyright 2005 __MyCompanyName__. All rights reserved. 
     6//  Copyright 2005 Steve Gehrman. All rights reserved. 
    77// 
    88 
  • trunk/NTSynchronousTask.m

    r105 r147  
    44// 
    55//  Created by Steve Gehrman on 9/29/05. 
    6 //  Copyright 2005 __MyCompanyName__. All rights reserved. 
     6//  Copyright 2005 Steve Gehrman. All rights reserved. 
    77// 
    88 
  • trunk/SUConstants.h

    r127 r147  
    2828extern NSString *SUSendProfileInfoKey; 
    2929 
     30extern NSString *SUSparkleErrorDomain; 
     31extern OSStatus SUFileCopyFailure; 
     32extern OSStatus SUAuthenticationFailure; 
     33extern OSStatus SUMissingUpdateError; 
     34extern OSStatus SUMissingInstallerToolError; 
     35 
    3036// NSInteger is a type that was added to Leopard. 
    3137// Here is some glue to ensure that NSInteger will work with pre-10.5 SDKs: 
  • trunk/SUConstants.m

    r127 r147  
    2525NSString *SUEnableAutomaticChecksKey = @"SUEnableAutomaticChecks"; 
    2626NSString *SUSendProfileInfoKey = @"SUSendProfileInfo"; 
     27 
     28NSString *SUSparkleErrorDomain = @"SUSparkleErrorDomain"; 
     29OSStatus SUFileCopyFailure = 9000; 
     30OSStatus SUAuthenticationFailure = 9001; 
     31OSStatus SUMissingUpdateError = 9002; 
     32OSStatus SUMissingInstallerToolError = 9003; 
  • trunk/SUStandardVersionComparator.m

    r132 r147  
    44// 
    55//  Created by Andy Matuschak on 12/21/07. 
    6 //  Copyright 2007 __MyCompanyName__. All rights reserved. 
     6//  Copyright 2007 Andy Matuschak. All rights reserved. 
    77// 
    88 
  • trunk/SUStatusController.m

    r136 r147  
    103103        maxProgressValue = value; 
    104104        [self setProgressValue:0]; 
     105        [progressBar setIndeterminate:(value == 0)]; 
    105106} 
    106107 
  • trunk/SUSystemProfiler.h

    r114 r147  
    44// 
    55//  Created by Andy Matuschak on 12/22/07. 
    6 //  Copyright 2007 __MyCompanyName__. All rights reserved. 
     6//  Copyright 2007 Andy Matuschak. All rights reserved. 
    77// 
    88 
  • trunk/SUUnarchiver.h

    r99 r147  
    1212@interface SUUnarchiver : NSObject { 
    1313        id delegate; 
     14        NSString *archivePath; 
    1415} 
    1516 
    1617- (void)unarchivePath:(NSString *)path; 
    1718- (void)setDelegate:delegate; 
     19- (void)cleanUp; 
    1820 
    1921@end 
  • trunk/SUUnarchiver.m

    r96 r147  
    1414 
    1515// This method abstracts the types that use a command line tool piping data from stdin. 
    16 - (BOOL)_extractArchivePath:archivePath pipingDataToCommand:(NSString *)command 
     16- (BOOL)_extractArchivePipingDataToCommand:(NSString *)command 
    1717{ 
    1818        // Get the file size. 
     
    5353} 
    5454 
    55 - (BOOL)_extractTAR:(NSString *)archivePath 
     55- (BOOL)_extractTAR 
    5656{ 
    57         return [self _extractArchivePath:archivePath pipingDataToCommand:@"tar -xC \"$DESTINATION\""]; 
     57        return [self _extractArchivePipingDataToCommand:@"tar -xC \"$DESTINATION\""]; 
    5858} 
    5959 
    60 - (BOOL)_extractTGZ:(NSString *)archivePath 
     60- (BOOL)_extractTGZ 
    6161{ 
    62         return [self _extractArchivePath:archivePath pipingDataToCommand:@"tar -zxC \"$DESTINATION\""]; 
     62        return [self _extractArchivePipingDataToCommand:@"tar -zxC \"$DESTINATION\""]; 
    6363} 
    6464 
    65 - (BOOL)_extractTBZ:(NSString *)archivePath 
     65- (BOOL)_extractTBZ 
    6666{ 
    67         return [self _extractArchivePath:archivePath pipingDataToCommand:@"tar -jxC \"$DESTINATION\""]; 
     67        return [self _extractArchivePipingDataToCommand:@"tar -jxC \"$DESTINATION\""]; 
    6868} 
    6969 
    70 - (BOOL)_extractZIP:(NSString *)archivePath 
     70- (BOOL)_extractZIP 
    7171{ 
    72         return [self _extractArchivePath:archivePath pipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""]; 
     72        return [self _extractArchivePipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""]; 
    7373} 
    7474 
    75 - (BOOL)_extractDMG:(NSString *)archivePath 
     75- (BOOL)_extractDMG 
    7676{                
    7777        // get a unique mount point path 
     
    100100} 
    101101 
    102 - (void)_unarchivePath:(NSString *)path 
     102- (void)_unarchive 
    103103{ 
    104104        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 
     
    108108        // we should continue with the update; returns NO if an error occurred. 
    109109        NSDictionary *commandDictionary = [NSDictionary dictionaryWithObjectsAndKeys: 
    110                                                                                                                                    @"_extractTBZ:", @".tbz", 
    111                                                                                                                                    @"_extractTBZ:", @".tar.bz2", 
    112                                                                                                                                    @"_extractTGZ:", @".tgz", 
    113                                                                                                                                    @"_extractTGZ:", @".tar.gz", 
    114                                                                                                                                    @"_extractTAR:", @".tar",  
    115                                                                                                                                    @"_extractZIP:", @".zip",  
    116                                                                                                                                    @"_extractDMG:", @".dmg", 
     110                                                                                                                                   @"_extractTBZ", @".tbz", 
     111                                                                                                                                   @"_extractTBZ", @".tar.bz2", 
     112                                                                                                                                   @"_extractTGZ", @".tgz", 
     113                                                                                                                                   @"_extractTGZ", @".tar.gz", 
     114                                                                                                                                   @"_extractTAR", @".tar",  
     115                                                                                                                                   @"_extractZIP", @".zip",  
     116                                                                                                                                   @"_extractDMG", @".dmg", 
    117117                                                                                                                                   nil]; 
    118118        SEL command = NULL; 
    119         NSString *theLastPathComponent = [[path lastPathComponent] lowercaseString]; 
     119        NSString *theLastPathComponent = [[archivePath lastPathComponent] lowercaseString]; 
    120120        NSEnumerator *theEnumerator = [[commandDictionary allKeys] objectEnumerator]; 
    121121        NSString *theExtension = NULL; 
    122122        while ((theExtension = [theEnumerator nextObject]) != NULL) 
     123        { 
     124                if ([[theLastPathComponent substringFromIndex:[theLastPathComponent length] - [theExtension length]] isEqualToString:theExtension]) 
    123125                { 
    124                 if ([[theLastPathComponent substringFromIndex:[theLastPathComponent length] - [theExtension length]] isEqualToString:theExtension]) 
    125                         { 
    126126                        command = NSSelectorFromString([commandDictionary objectForKey:theExtension]); 
    127127                        break; 
    128                         } 
    129128                } 
     129        } 
    130130         
    131131        BOOL result; 
     
    134134                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:command]]; 
    135135                [invocation setSelector:command]; 
    136                 [invocation setArgument:&path atIndex:2]; // 0 and 1 are private! 
    137136                [invocation invokeWithTarget:self]; 
    138137                [invocation getReturnValue:&result]; 
     
    157156- (void)unarchivePath:(NSString *)path 
    158157{ 
    159         [NSThread detachNewThreadSelector:@selector(_unarchivePath:) toTarget:self withObject:path]; 
     158        archivePath = [path copy]; 
     159        [NSThread detachNewThreadSelector:@selector(_unarchive) toTarget:self withObject:nil]; 
    160160} 
    161161 
     
    165165} 
    166166 
     167- (void)cleanUp 
     168{ 
     169        if ([[archivePath pathExtension] isEqualToString:@".dmg"]) 
     170        { 
     171                [NSTask launchedTaskWithLaunchPath:@"/usr/bin/hdiutil" arguments:[NSArray arrayWithObjects:@"detach", [archivePath stringByDeletingLastPathComponent], @"-force", nil]];         
     172        } 
     173        [[NSFileManager defaultManager] removeFileAtPath:archivePath handler:nil]; 
     174} 
     175 
     176- (void)dealloc 
     177{ 
     178        [archivePath release]; 
     179        [super dealloc]; 
     180} 
     181 
    167182@end 
  • trunk/SUUpdater.h

    r114 r147  
    1616// .zip, .dmg, .tar, .tbz, .tgz archives are supported at this time. 
    1717 
    18 @class SUAppcastItem, SUUpdateAlert, SUStatusController
     18@class SUAppcastItem, SUUpdateAlert, SUStatusController, SUUnarchiver
    1919@interface SUUpdater : NSObject { 
    2020        SUAppcastItem *updateItem; 
     
    3434        NSBundle *hostBundle; 
    3535        id delegate; 
     36         
     37        SUUnarchiver *unarchiver; 
    3638} 
    3739 
  • trunk/SUUpdater.m

    r144 r147  
    437437 
    438438- (void)unarchiverDidFinish:(SUUnarchiver *)ua 
    439 
    440         [ua autorelease]; 
    441          
     439{        
    442440        if ([self isAutomaticallyUpdating]) 
    443441        { 
     
    455453- (void)unarchiverDidFail:(SUUnarchiver *)ua 
    456454{ 
    457         [ua autorelease]; 
     455        [ua release]; 
    458456        [self showUpdateErrorAlertWithInfo:SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil)]; 
    459457        [self abandonUpdate]; 
     
    478476                } 
    479477                 
    480                 SUUnarchiver *unarchiver = [[SUUnarchiver alloc] init]; 
     478                unarchiver = [[SUUnarchiver alloc] init]; 
    481479                [unarchiver setDelegate:self]; 
    482480                [unarchiver unarchivePath:downloadPath]; // asynchronous extraction! 
     
    495493        [statusController beginActionWithTitle:SULocalizedString(@"Installing update...", nil) maxProgressValue:0 statusText:nil]; 
    496494        [statusController setButtonEnabled:NO]; 
    497          
    498         // Hack to force us to wait for the UI to update. 
    499         NSEvent *event; 
    500         while((event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])) 
    501                 [NSApp sendEvent:event];         
    502  
    503         // Search subdirectories for the application 
    504         NSString *currentFile, *newAppDownloadPath = nil, *bundleFileName = [[hostBundle bundlePath] lastPathComponent]; 
    505         BOOL isPackage = NO; 
    506         NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:[downloadPath stringByDeletingLastPathComponent]]; 
    507         while ((currentFile = [dirEnum nextObject])) 
    508         { 
    509                 // Some DMGs have symlinks into /Applications! That's no good! And there's no point in looking in bundles. 
    510                 if ([[NSFileManager defaultManager] isAliasFolderAtPath:[[downloadPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:currentFile]] || 
    511                         [[currentFile pathExtension] isEqualToString:[[hostBundle bundlePath] pathExtension]] || 
    512                         [[currentFile pathExtension] isEqualToString:@"pkg"] || 
    513                         [[currentFile pathExtension] isEqualToString:@"mpkg"]) 
    514                 { 
    515                         [dirEnum skipDescendents]; 
    516                 } 
    517                  
    518                 if ([[currentFile lastPathComponent] isEqualToString:bundleFileName]) // We found one! 
    519                 { 
    520                         isPackage = NO; 
    521                         newAppDownloadPath = [[downloadPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:currentFile]; 
    522                         break; 
    523                 } 
    524                 else if (([[currentFile pathExtension] isEqualToString:@"pkg"] || [[currentFile pathExtension] isEqualToString:@"mpkg"]) && 
    525                                   [[[currentFile lastPathComponent] stringByDeletingPathExtension] isEqualToString:[bundleFileName stringByDeletingPathExtension]]) 
    526                 { 
    527                         isPackage = YES; 
    528                         newAppDownloadPath = [[downloadPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:currentFile]; 
    529                         break; 
    530                 } 
    531         } 
    532          
    533         if (![[NSFileManager defaultManager] fileExistsAtPath:newAppDownloadPath]) 
    534         { 
    535                 NSLog(@"The update archive didn't contain an application or package with the proper name: %@ or %@.", bundleFileName, [[bundleFileName stringByDeletingPathExtension] stringByAppendingPathComponent:@".[m]pkg"]); 
    536                 [self showUpdateErrorAlertWithInfo:SULocalizedString(@"An error occurred during installation. Please try again later.", nil)]; 
    537                 [self abandonUpdate]; 
    538                 return;          
    539         } 
    540          
    541         // Alright, *now* we can actually install the new version. 
    542         int processIdentifierToWatch = [[NSProcessInfo processInfo] processIdentifier]; 
    543         if (isPackage) 
    544         { 
    545                 NSString *installerPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.apple.installer"]; 
    546                 installerPath = [installerPath stringByAppendingString:@"/Contents/MacOS/Installer"]; 
    547                 NSTask *installer = [NSTask launchedTaskWithLaunchPath:installerPath arguments:[NSArray arrayWithObjects:newAppDownloadPath, nil]]; 
    548                 processIdentifierToWatch = [installer processIdentifier]; // We want to wait until the installer quits. 
    549         } 
    550         else 
    551         { 
    552                 if (![[NSFileManager defaultManager] copyPath:newAppDownloadPath 
    553                                                                                          overPath:[hostBundle bundlePath] 
    554                                                                    withAuthentication:![self isAutomaticallyUpdating]]) 
    555                 { 
    556                         [self showUpdateErrorAlertWithInfo:[NSString stringWithFormat:SULocalizedString(@"%@ can't install the update! Make sure you have enough disk space.", nil), [hostBundle name]]]; 
    557                         [self abandonUpdate]; 
    558                         return; 
    559                 } 
    560         } 
    561495         
    562496        // Prompt for permission to restart if we're automatically updating. 
     
    571505        } 
    572506         
    573         // This is really sloppy and coupled, but I can't think of a better way to deal with this. 
    574         // If we've got a DMG, we've mounted it; now we've got to unmount it. 
    575         if ([[[downloadPath pathExtension] lowercaseString] isEqualToString:@"dmg"]) 
    576         { 
    577                 [NSTask launchedTaskWithLaunchPath:@"/usr/bin/hdiutil" arguments:[NSArray arrayWithObjects:@"detach", [newAppDownloadPath stringByDeletingLastPathComponent], @"-force", nil]];  
    578         } 
    579          
     507        [SUInstaller installFromUpdateFolder:[downloadPath stringByDeletingLastPathComponent] overHostBundle:hostBundle delegate:self]; 
     508
     509         
     510- (void)installerFinishedForHostBundle:(NSBundle *)hb 
     511
     512        if (hb != hostBundle) { return; } 
     513         
     514        [unarchiver cleanUp]; 
    580515        [[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self]; 
    581516         
    582517        NSString *relaunchPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"relaunch" ofType:nil]; 
    583         @try { 
    584                 [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:[hostBundle bundlePath], [NSString stringWithFormat:@"%d", processIdentifierToWatch], nil]]; 
    585         } @catch (NSException *e) { 
    586                 NSLog(@"relaunch error: %@", e); 
     518        @try 
     519        { 
     520                [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:[hostBundle bundlePath], [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], nil]]; 
     521        } 
     522        @catch (NSException *e) 
     523        { 
     524                NSLog(@"Sparkle relaunch error: %@", e); 
     525                [self showUpdateErrorAlertWithInfo:[NSString stringWithFormat:SULocalizedString(@"Couldn't restart %1$@, but the new version will be available next time you run %1$@.", nil), [hostBundle name]]]; 
     526                [self abandonUpdate]; 
    587527        } 
    588528        [NSApp terminate:self]; 
     529} 
     530 
     531- (void)installerForHostBundle:(NSBundle *)hb failedWithError:(NSError *)error 
     532{ 
     533        if (hb != hostBundle) { return; } 
     534        NSLog(@"Sparkle Installation Error: %@", [error localizedDescription]); 
     535        [self showUpdateErrorAlertWithInfo:SULocalizedString(@"An error occurred during installation. Please try again later.", nil)]; 
     536        [self abandonUpdate]; 
    589537} 
    590538 
  • trunk/Sparkle.h

    r127 r147  
    4040#import "SUAutomaticUpdateAlert.h" 
    4141#import "SUConstants.h" 
     42#import "SUInstaller.h" 
    4243#import "SUStandardVersionComparator.h" 
    4344#import "SUStatusChecker.h" 
  • trunk/Sparkle.xcodeproj/project.pbxproj

    r146 r147  
    3535                6171D9070D57B81800BFE886 /* NSFileManager+Aliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 6171D9050D57B81800BFE886 /* NSFileManager+Aliases.h */; settings = {ATTRIBUTES = (Public, ); }; }; 
    3636                6171D9080D57B81800BFE886 /* NSFileManager+Aliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 6171D9060D57B81800BFE886 /* NSFileManager+Aliases.m */; }; 
     37                618FA5010DAE88B40026945C /* SUInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 618FA4FF0DAE88B40026945C /* SUInstaller.h */; }; 
     38                618FA5020DAE88B40026945C /* SUInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5000DAE88B40026945C /* SUInstaller.m */; }; 
     39                618FA5050DAE8AB80026945C /* SUPlainInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 618FA5030DAE8AB80026945C /* SUPlainInstaller.h */; }; 
     40                618FA5060DAE8AB80026945C /* SUPlainInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5040DAE8AB80026945C /* SUPlainInstaller.m */; }; 
     41                618FA5220DAE8E8A0026945C /* SUPackageInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 618FA5200DAE8E8A0026945C /* SUPackageInstaller.h */; }; 
     42                618FA5230DAE8E8A0026945C /* SUPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5210DAE8E8A0026945C /* SUPackageInstaller.m */; }; 
    3743                6196CFF909C72148000DC222 /* SUStatusController.h in Headers */ = {isa = PBXBuildFile; fileRef = 6196CFE309C71ADE000DC222 /* SUStatusController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 
    3844                6196CFFA09C72149000DC222 /* SUStatusController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6196CFE409C71ADE000DC222 /* SUStatusController.m */; }; 
     
    150156                6171D9050D57B81800BFE886 /* NSFileManager+Aliases.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+Aliases.h"; sourceTree = "<group>"; }; 
    151157                6171D9060D57B81800BFE886 /* NSFileManager+Aliases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+Aliases.m"; sourceTree = "<group>"; }; 
     158                618FA4FF0DAE88B40026945C /* SUInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUInstaller.h; sourceTree = "<group>"; }; 
     159                618FA5000DAE88B40026945C /* SUInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUInstaller.m; sourceTree = "<group>"; }; 
     160                618FA5030DAE8AB80026945C /* SUPlainInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUPlainInstaller.h; sourceTree = "<group>"; }; 
     161                618FA5040DAE8AB80026945C /* SUPlainInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUPlainInstaller.m; sourceTree = "<group>"; }; 
     162                618FA5200DAE8E8A0026945C /* SUPackageInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUPackageInstaller.h; sourceTree = "<group>"; }; 
     163                618FA5210DAE8E8A0026945C /* SUPackageInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUPackageInstaller.m; sourceTree = "<group>"; }; 
    152164                6196CFE309C71ADE000DC222 /* SUStatusController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUStatusController.h; sourceTree = "<group>"; }; 
    153165                6196CFE409C71ADE000DC222 /* SUStatusController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUStatusController.m; sourceTree = "<group>"; }; 
     
    366378                                61299B3809CB053D00B7442F /* Cocoa Categories */, 
    367379                                61299B3A09CB056100B7442F /* User Interface */, 
     380                                618FA6DB0DB485440026945C /* Installation */, 
    368381                                61B5F8F309C4CE5900B25A18 /* Utilities */, 
    369382                                61B5F8E309C4CE3C00B25A18 /* SUUpdater.h */, 
     
    430443                        ); 
    431444                        name = "User Interface";&nb