Changeset 153

Show
Ignore:
Timestamp:
05/07/08 16:12:41 (1 week ago)
Author:
andym
Message:

Touched practically every line of code in a super-monster-awesome refactoring. Please read:
- Broke out SUUpdater functionality into update drivers. There's a basic one from which everything else inherits, then a user-initiated one, an automatic one, and a scheduled one. It's super-clean-and-shiny.
- Destroyed the abomination that was SUStatusChecker. In its place is SUProbingUpdateDriver, which is like 10 lines long.
- Made automatic installation less stupid. It used to install, THEN offer to relaunch. That's dumb, beacuse if the user says no, the app is running from the trash. Now it offers to install and relaunch or to install on quit.
- Renamed like every method and symbol. I hope you didn't branch anything.
- Reorganized the project hierarchy to be much clearer and easier to navigate.
- Reworked the error system to no use NSError instead of exceptions; extra technical information is now logged to the console so that we can find problems.
- A bunch of other small bugfixes in things I noticed along the way but no longer remember.
- Probably a ton of other stuff.
Read over the code and see what I've done. Then PLEASE test this with your app internally and let me know how it goes. This revision is hereby NOT YET DECLARED SAFE FOR PUBLIC RELEASE. But because I'm still using SVN, this is how things have to be.

Files:

Legend:

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

    r121 r153  
    77// 
    88 
     9#ifndef NSFILEMANAGER_PLUS_ALIASES_H 
     10#define NSFILEMANAGER_PLUS_ALIASES_H 
     11 
    912#import <Cocoa/Cocoa.h> 
    1013 
     
    1215- (BOOL)isAliasFolderAtPath:(NSString *)path; 
    1316@end 
     17 
     18#endif 
  • trunk/NSFileManager+Authentication.m

    r147 r153  
    8989        else 
    9090                postFix = @"old"; 
    91         return [[[path stringByDeletingPathExtension] stringByAppendingFormat:@" (%@)", postFix] stringByAppendingPathExtension:[path pathExtension]]; 
     91        NSString *prefix = [[path stringByDeletingPathExtension] stringByAppendingFormat:@" (%@)", postFix]; 
     92        NSString *tempDir = [prefix stringByAppendingPathExtension:[path pathExtension]]; 
     93        // Now let's make sure we get a unique path. 
     94        int cnt=2; 
     95        while ([[NSFileManager defaultManager] fileExistsAtPath:tempDir] && cnt <= 999999) 
     96                tempDir = [NSString stringWithFormat:@"%@ %d.%@", prefix, cnt++, [path pathExtension]]; 
     97        return tempDir; 
    9298} 
    9399 
     
    207213 
    208214        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                 } 
     215 
     216        if (![[NSFileManager defaultManager] movePath:dst toPath:tmpPath handler:self]) 
     217        { 
     218                if (error != NULL) 
     219                        *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't move %@ to %@.", dst, tmpPath] forKey:NSLocalizedDescriptionKey]]; 
     220                return NO;                       
     221        } 
     222        if (![[NSFileManager defaultManager] copyPath:src toPath:dst handler:self]) 
     223        { 
     224                if (error != NULL) 
     225                        *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't copy %@ to %@.", src, dst] forKey:NSLocalizedDescriptionKey]]; 
     226                return NO;                       
    230227        } 
    231228         
  • trunk/NSFileManager+ExtendedAttributes.h

    r143 r153  
    66//  Copyright 2008 Mark Mentovai.  All rights reserved. 
    77// 
     8 
     9#ifndef NSFILEMANAGER_PLUS_EXTENDEDATTRIBUTES 
     10#define NSFILEMANAGER_PLUS_EXTENDEDATTRIBUTES 
    811 
    912#import <Cocoa/Cocoa.h> 
     
    4750 
    4851@end 
     52 
     53#endif 
  • trunk/RSS.h

    r99 r153  
    6262 
    6363/*Public*/ 
     64- (RSS *)initWithURL:(NSURL *) url normalize:(BOOL) fl userAgent:(NSString*)userAgent error:(NSError **)error; 
    6465 
    6566- (RSS *) initWithTitle: (NSString *) title andDescription: (NSString *) description; 
    6667 
    6768- (RSS *) initWithData: (NSData *) rssData normalize: (BOOL) fl; 
    68  
    69 - (RSS *) initWithURL: (NSURL *) url normalize: (BOOL) fl; 
    70 - (RSS *) initWithURL: (NSURL *) url normalize: (BOOL) fl userAgent:(NSString *)userAgent; 
    7169 
    7270- (NSDictionary *) headerItems; 
  • trunk/RSS.m

    r105 r153  
    139139 
    140140 
    141 - (RSS *) initWithURL: (NSURL *) url normalize: (BOOL) fl 
     141          
     142         
     143- (RSS *)initWithURL:(NSURL *)url normalize:(BOOL)fl userAgent:(NSString*)userAgent error:(NSError **)error 
    142144{ 
    143         return [self initWithURL: url normalize: fl userAgent: nil]; 
    144 } 
    145  
    146           
    147          
    148 - (RSS *) initWithURL: (NSURL *) url normalize: (BOOL) fl userAgent: (NSString*)userAgent 
    149 { 
    150         NSData *rssData; 
    151  
    152145        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url cachePolicy: NSURLRequestReloadIgnoringCacheData 
    153146                                                                                timeoutInterval: 30.0]; 
     
    155148                [request setValue: userAgent forHTTPHeaderField: @"User-Agent"]; 
    156149                         
    157         NSURLResponse *response=0; 
    158         NSError *error=0; 
    159  
    160         rssData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error]; 
    161          
    162         if (rssData == nil) 
     150        NSURLResponse *response = nil; 
     151        NSData *rssData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error]; 
     152        if (rssData == nil) { return nil; } 
     153         
     154        @try 
    163155        { 
    164     NSString *failureReason; 
    165     if ([error respondsToSelector:@selector(localizedFailureReason)]) 
    166       failureReason = [error localizedFailureReason]; 
    167     else 
    168       failureReason = [error localizedDescription]; 
    169                 NSException *exception = [NSException exceptionWithName: @"RSSDownloadFailed" 
    170                                                                                                                  reason: failureReason userInfo: [error userInfo] ]; 
    171                 [exception raise]; 
     156                [self initWithData:rssData normalize:fl]; 
    172157        } 
    173          
    174         return [self initWithData: rssData normalize: fl];       
    175 } /*initWithUrl*/ 
     158        @catch (NSException *parseException) 
     159        { 
     160                if (error) 
     161                        *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastParseError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while parsing the update feed.", nil), NSLocalizedDescriptionKey, [parseException reason], NSLocalizedFailureReasonErrorKey, nil]]; 
     162                return nil; 
     163        } 
     164        return self; 
     165
    176166 
    177167 
  • trunk/SUAppcast.h

    r114 r153  
    1313@interface SUAppcast : NSObject { 
    1414        NSArray *items; 
     15        NSString *userAgentString; 
    1516        id delegate; 
    1617} 
    1718 
    18 - (void)fetchAppcastFromURL:(NSURL *)url parameters:(NSArray *)parameters
     19- (void)fetchAppcastFromURL:(NSURL *)url
    1920- (void)setDelegate:delegate; 
     21- (void)setUserAgentString:(NSString *)userAgentString; 
    2022 
    2123- (SUAppcastItem *)newestItem; 
     
    2628@interface NSObject (SUAppcastDelegate) 
    2729- (void)appcastDidFinishLoading:(SUAppcast *)appcast; 
    28 - (void)appcastDidFailToLoad:(SUAppcast *)appcast; 
    29 - (NSString *)userAgentForAppcast:(SUAppcast *)appcast; 
     30- (void)appcast:(SUAppcast *)appcast failedToLoadWithError:(NSError *)error; 
    3031@end 
    3132 
  • trunk/SUAppcast.m

    r114 r153  
    1010#import "SUAppcast.h" 
    1111 
    12 @interface NSURL (SUParameterAdditions) 
    13 - (NSURL *)URLWithParameters:(NSArray *)parameters; 
    14 @end 
    15  
    16 @implementation NSURL (SUParameterAdditions) 
    17 - (NSURL *)URLWithParameters:(NSArray *)parameters; 
    18 { 
    19         if (parameters == nil || [parameters count] == 0) { return self; } 
    20         NSMutableArray *profileInfo = [NSMutableArray array]; 
    21         NSEnumerator *profileInfoEnumerator = [parameters objectEnumerator]; 
    22         NSDictionary *currentProfileInfo; 
    23         while ((currentProfileInfo = [profileInfoEnumerator nextObject])) { 
    24                 [profileInfo addObject:[NSString stringWithFormat:@"%@=%@", [currentProfileInfo objectForKey:@"key"], [currentProfileInfo objectForKey:@"value"]]]; 
    25         } 
    26          
    27         NSString *appcastStringWithProfile = [NSString stringWithFormat:@"%@?%@", [self absoluteString], [profileInfo componentsJoinedByString:@"&"]]; 
    28          
    29         // Clean it up so it's a valid URL 
    30         return [NSURL URLWithString:[appcastStringWithProfile stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 
    31 } 
    32 @end 
    33  
    34  
    3512@implementation SUAppcast 
    3613 
    37 - (void)fetchAppcastFromURL:(NSURL *)url parameters:(NSArray *)parameters 
     14- (void)fetchAppcastFromURL:(NSURL *)url 
    3815{ 
    39         [NSThread detachNewThreadSelector:@selector(_fetchAppcastFromURL:) toTarget:self withObject:[url URLWithParameters:parameters]]; // let's not block the main thread 
    40 
    41  
    42 - (void)setDelegate:del 
    43 
    44         delegate = del; 
     16        [NSThread detachNewThreadSelector:@selector(_fetchAppcastFromURL:) toTarget:self withObject:url]; 
    4517} 
    4618 
     
    5325- (SUAppcastItem *)newestItem 
    5426{ 
    55         return [items objectAtIndex:0]; // the RSS class takes care of sorting by published date, descending. 
     27        if ([items count] > 0) 
     28                return [items objectAtIndex:0]; // the RSS class takes care of sorting by published date, descending. 
     29        else 
     30                return nil; 
    5631} 
    5732 
     
    6540        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 
    6641         
    67         RSS *feed = [RSS alloc]; 
     42        NSError *error = nil; 
     43        RSS *feed = [[RSS alloc] initWithURL:url normalize:YES userAgent:userAgentString error:&error]; 
     44        if (!feed) 
     45        { 
     46                [self performSelectorOnMainThread:@selector(reportError:) withObject:error waitUntilDone:NO]; 
     47                return; 
     48        } 
     49                 
     50        // Set up all the appcast items: 
     51        items = [NSMutableArray array]; 
     52        id enumerator = [[feed newsItems] objectEnumerator], current; 
    6853        @try 
    6954        { 
    70                 NSString *userAgent = nil; 
    71                 if ([delegate respondsToSelector:@selector(userAgentForAppcast:)]) 
    72                         userAgent = [delegate userAgentForAppcast:self]; 
    73                  
    74                 feed = [feed initWithURL:url normalize:YES userAgent:userAgent]; 
    75                 if (!feed) 
    76                         [NSException raise:@"SUFeedException" format:@"Couldn't fetch feed from server."]; 
    77                  
    78                 // Set up all the appcast items 
    79                 NSMutableArray *tempItems = [NSMutableArray array]; 
    80                 id enumerator = [[feed newsItems] objectEnumerator], current; 
    8155                while ((current = [enumerator nextObject])) 
    8256                { 
    83                         [tempItems addObject:[[[SUAppcastItem alloc] initWithDictionary:current] autorelease]]; 
     57                        [(NSMutableArray *)items addObject:[[[SUAppcastItem alloc] initWithDictionary:current] autorelease]]; 
    8458                } 
    85                 items = [[NSArray arrayWithArray:tempItems] retain]; 
     59        } 
     60        @catch (NSException *parseException) 
     61        { 
     62                error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastParseError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while parsing the update feed.", nil), NSLocalizedDescriptionKey, [parseException reason], SUTechnicalErrorInformationKey, nil]]; 
     63                [self performSelectorOnMainThread:@selector(reportError:) withObject:error waitUntilDone:NO]; 
     64                return; 
     65        } 
     66        items = [[NSArray arrayWithArray:items] retain]; // Make the items list immutable. 
     67         
     68        if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:)]) 
     69                [delegate performSelectorOnMainThread:@selector(appcastDidFinishLoading:) withObject:self waitUntilDone:NO]; 
    8670                 
    87                 if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:)]) 
    88                         [delegate performSelectorOnMainThread:@selector(appcastDidFinishLoading:) withObject:self waitUntilDone:NO]; 
    89                  
    90         } 
    91         @catch (NSException *e) 
     71        [feed release]; 
     72        [pool release]; 
     73
     74 
     75- (void)reportError:(NSError *)error 
     76
     77        if ([delegate respondsToSelector:@selector(appcast:failedToLoadWithError:)]) 
    9278        { 
    93                 if ([delegate respondsToSelector:@selector(appcastDidFailToLoad:)]) 
    94                         [delegate performSelectorOnMainThread:@selector(appcastDidFailToLoad:) withObject:self waitUntilDone:NO]; 
    95         } 
    96         @finally 
    97         { 
    98                 [feed release]; 
    99                 [pool release];  
     79                [delegate appcast:self failedToLoadWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred in retrieving update information. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]]; 
    10080        } 
    10181} 
    10282 
     83- (void)setUserAgentString:(NSString *)uas 
     84{ 
     85        [userAgentString release]; 
     86        userAgentString = [uas copy]; 
     87} 
     88 
     89- (void)setDelegate:del 
     90{ 
     91        delegate = del; 
     92} 
     93 
    10394@end 
  • trunk/SUAppcastItem.h

    r99 r153  
    1818        NSURL *releaseNotesURL; 
    1919         
    20         NSString *DSASignature; 
    21         NSString *MD5Sum; 
    22          
     20        NSString *DSASignature;  
    2321        NSString *minimumSystemVersion; 
    2422         
    2523        NSURL *fileURL; 
    26         NSString *fileVersion; 
    2724        NSString *versionString; 
     25        NSString *displayVersionString; 
    2826} 
    2927 
     
    3230 
    3331- (NSString *)title; 
    34 - (void)setTitle:(NSString *)aTitle
    35  
     32- (NSString *)versionString
     33- (NSString *)displayVersionString; 
    3634- (NSDate *)date; 
    37 - (void)setDate:(NSDate *)aDate; 
    38  
    3935- (NSString *)description; 
    40 - (void)setDescription:(NSString *)aDescription; 
    41  
    4236- (NSURL *)releaseNotesURL; 
    43 - (void)setReleaseNotesURL:(NSURL *)aReleaseNotesURL; 
    44  
     37- (NSURL *)fileURL; 
    4538- (NSString *)DSASignature; 
    46 - (void)setDSASignature:(NSString *)aDSASignature; 
    47  
    48 - (NSString *)MD5Sum; 
    49 - (void)setMD5Sum:(NSString *)aMd5Sum; 
    50  
    51 - (NSURL *)fileURL; 
    52 - (void)setFileURL:(NSURL *)aFileURL; 
    53  
    54 - (NSString *)fileVersion; 
    55 - (void)setFileVersion:(NSString *)aFileVersion; 
    56  
    57 - (NSString *)versionString; 
    58 - (void)setVersionString:(NSString *)versionString; 
    59  
    6039- (NSString *)minimumSystemVersion; 
    61 - (void)setMinimumSystemVersion:(NSString *)systemVersionString; 
    6240 
    6341@end 
  • trunk/SUAppcastItem.m

    r110 r153  
    1111 
    1212@implementation SUAppcastItem 
    13  
    14 - initWithDictionary:(NSDictionary *)dict 
    15 { 
    16         self = [super init]; 
    17         if (self) 
    18         { 
    19                 [self setTitle:[dict objectForKey:@"title"]]; 
    20                 [self setDate:[dict objectForKey:@"pubDate"]]; 
    21                 [self setDescription:[dict objectForKey:@"description"]]; 
    22                  
    23                 id enclosure = [dict objectForKey:@"enclosure"]; 
    24                 [self setDSASignature:[enclosure objectForKey:@"sparkle:dsaSignature"]]; 
    25                 [self setMD5Sum:[enclosure objectForKey:@"sparkle:md5Sum"]]; 
    26                  
    27                 [self setFileURL:[NSURL URLWithString:[[enclosure objectForKey:@"url"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; 
    28                  
    29                 // Find the appropriate release notes URL. 
    30                 if ([dict objectForKey:@"sparkle:releaseNotesLink"]) 
    31                 { 
    32                         [self setReleaseNotesURL:[NSURL URLWithString:[dict objectForKey:@"sparkle:releaseNotesLink"]]]; 
    33                 } 
    34                 else if ([[self description] hasPrefix:@"http://"]) // if the description starts with http://, use that. 
    35                 { 
    36                         [self setReleaseNotesURL:[NSURL URLWithString:[self description]]]; 
    37                 } 
    38                 else 
    39                 { 
    40                         [self setReleaseNotesURL:nil]; 
    41                 } 
    42                  
    43                 NSString *minVersion = [dict objectForKey:@"sparkle:minimumSystemVersion"]; 
    44                 if(minVersion) 
    45                         [self setMinimumSystemVersion:minVersion]; 
    46                 else 
    47                         [self setMinimumSystemVersion:@"10.3.0"];//sparkle doesn't run on 10.2-, so we don't have to worry about it 
    48                  
    49                 // Try to find a version string. 
    50                 // Finding the new version number from the RSS feed is a little bit hacky. There are two ways: 
    51                 // 1. A "sparkle:version" attribute on the enclosure tag, an extension from the RSS spec. 
    52                 // 2. If there isn't a version attribute, Sparkle will parse the path in the enclosure, expecting 
    53                 //    that it will look like this: http://something.com/YourApp_0.5.zip. It'll read whatever's between the last 
    54                 //    underscore and the last period as the version number. So name your packages like this: APPNAME_VERSION.extension. 
    55                 //    The big caveat with this is that you can't have underscores in your version strings, as that'll confuse Sparkle. 
    56                 //    Feel free to change the separator string to a hyphen or something more suited to your needs if you like. 
    57                 NSString *newVersion = [enclosure objectForKey:@"sparkle:version"]; 
    58                 if (!newVersion) // no sparkle:version attribute 
    59                 { 
    60                         // Separate the url by underscores and take the last component, as that'll be closest to the end, 
    61                         // then we remove the extension. Hopefully, this will be the version. 
    62                         NSArray *fileComponents = [[enclosure objectForKey:@"url"] componentsSeparatedByString:@"_"]; 
    63                         if ([fileComponents count] > 1) 
    64                                 newVersion = [[fileComponents lastObject] stringByDeletingPathExtension]; 
    65                 } 
    66                 [self setFileVersion:newVersion]; 
    67                  
    68                 NSString *shortVersionString = [enclosure objectForKey:@"sparkle:shortVersionString"]; 
    69                 if (shortVersionString) 
    70                         [self setVersionString:shortVersionString]; 
    71                 else 
    72                         [self setVersionString:[self fileVersion]]; 
    73         } 
    74          
    75         return self; 
    76 } 
    7713 
    7814// Attack of accessors! 
     
    12157    DSASignature = [aDSASignature copy]; 
    12258} 
    123  
    124  
    125 - (NSString *)MD5Sum { return [[MD5Sum retain] autorelease]; } 
    126  
    127 - (void)setMD5Sum:(NSString *)aMD5Sum 
    128 
    129     [MD5Sum release]; 
    130     MD5Sum = [aMD5Sum copy]; 
    131 
    132  
     59                         
    13360 
    13461- (NSURL *)fileURL { return [[fileURL retain] autorelease]; } 
     
    14168 
    14269 
    143 - (NSString *)fileVersion { return [[fileVersion retain] autorelease]; } 
     70- (NSString *)versionString { return [[versionString retain] autorelease]; } 
    14471 
    145 - (void)setFileVersion:(NSString *)aFileVersion 
     72- (void)setVersionString:(NSString *)s 
    14673{ 
    147     [fileVersion release]; 
    148     fileVersion = [aFileVersion copy]; 
     74    [versionString release]; 
     75    versionString = [s copy]; 
    14976} 
    15077 
    15178 
    152 - (NSString *)versionString { return [[versionString retain] autorelease]; } 
     79- (NSString *)displayVersionString { return [[displayVersionString retain] autorelease]; } 
    15380 
    154 - (void)setVersionString:(NSString *)aVersionString 
     81- (void)setDisplayVersionString:(NSString *)s 
    15582{ 
    156     [versionString release]; 
    157     versionString = [aVersionString copy]; 
     83    [displayVersionString release]; 
     84    displayVersionString = [s copy]; 
    15885} 
    15986 
     
    16693} 
    16794 
     95- initWithDictionary:(NSDictionary *)dict 
     96{ 
     97        self = [super init]; 
     98        if (self) 
     99        { 
     100                [self setTitle:[dict objectForKey:@"title"]]; 
     101                [self setDate:[dict objectForKey:@"pubDate"]]; 
     102                [self setDescription:[dict objectForKey:@"description"]]; 
     103                 
     104                id enclosure = [dict objectForKey:@"enclosure"]; 
     105                if (enclosure == nil || [enclosure objectForKey:@"url"] == nil) 
     106                        [NSException raise:@"SUAppcastException" format:@"Couldn't find an download URL for feed entry %@!", [self title]]; 
     107                [self setFileURL:[NSURL URLWithString:[[enclosure objectForKey:@"url"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; 
     108                [self setDSASignature:[enclosure objectForKey:@"sparkle:dsaSignature"]];                 
     109                 
     110                // Try to find a version string. 
     111                // Finding the new version number from the RSS feed is a little bit hacky. There are two ways: 
     112                // 1. A "sparkle:version" attribute on the enclosure tag, an extension from the RSS spec. 
     113                // 2. If there isn't a version attribute, Sparkle will parse the path in the enclosure, expecting 
     114                //    that it will look like this: http://something.com/YourApp_0.5.zip. It'll read whatever's between the last 
     115                //    underscore and the last period as the version number. So name your packages like this: APPNAME_VERSION.extension. 
     116                //    The big caveat with this is that you can't have underscores in your version strings, as that'll confuse Sparkle. 
     117                //    Feel free to change the separator string to a hyphen or something more suited to your needs if you like. 
     118                NSString *newVersion = [enclosure objectForKey:@"sparkle:version"]; 
     119                if (!newVersion) // no sparkle:version attribute 
     120                { 
     121                        // Separate the url by underscores and take the last component, as that'll be closest to the end, 
     122                        // then we remove the extension. Hopefully, this will be the version. 
     123                        NSArray *fileComponents = [[enclosure objectForKey:@"url"] componentsSeparatedByString:@"_"]; 
     124                        if ([fileComponents count] > 1) 
     125                                newVersion = [[fileComponents lastObject] stringByDeletingPathExtension]; 
     126                        else 
     127                                [NSException raise:@"SUAppcastException" format:@"Couldn't find a version string for %@! You need a sparkle:version attribute.", [enclosure objectForKey:@"url"]]; 
     128                } 
     129                [self setVersionString:newVersion]; 
     130                [self setMinimumSystemVersion:[dict objectForKey:@"sparkle:minimumSystemVersion"]]; 
     131                 
     132                NSString *shortVersionString = [enclosure objectForKey:@"sparkle:shortVersionString"]; 
     133                if (shortVersionString) 
     134                        [self setDisplayVersionString:shortVersionString]; 
     135                else 
     136                        [self setDisplayVersionString:[self versionString]]; 
     137                 
     138                // Find the appropriate release notes URL. 
     139                if ([dict objectForKey:@"sparkle:releaseNotesLink"]) 
     140                        [self setReleaseNotesURL:[NSURL URLWithString:[dict objectForKey:@"sparkle:releaseNotesLink"]]]; 
     141                else if ([[self description] hasPrefix:@"http://"]) // if the description starts with http://, use that. 
     142                        [self setReleaseNotesURL:[NSURL URLWithString:[self description]]]; 
     143                else 
     144                        [self setReleaseNotesURL:nil]; 
     145        } 
     146        return self; 
     147} 
    168148 
    169149- (void)dealloc 
     
    174154    [self setReleaseNotesURL:nil]; 
    175155    [self setDSASignature:nil]; 
    176     [self setMD5Sum:nil]; 
    177156    [self setFileURL:nil]; 
    178     [self setFileVersion:nil]; 
    179         [self setVersionString:nil]; 
     157    [self setVersionString:nil]; 
     158        [self setDisplayVersionString:nil]; 
    180159    [super dealloc]; 
    181160} 
  • trunk/SUAutomaticUpdateAlert.h

    r125 r153  
    1212#import "SUWindowController.h" 
    1313 
     14typedef enum 
     15{ 
     16        SUInstallNowChoice, 
     17        SUInstallLaterChoice, 
     18        SUDoNotInstallChoice 
     19} SUAutomaticInstallationChoice; 
     20 
    1421@class SUAppcastItem; 
    1522@interface SUAutomaticUpdateAlert : SUWindowController { 
    1623        SUAppcastItem *updateItem; 
     24        id delegate; 
    1725        NSBundle *hostBundle; 
    1826} 
    1927 
    20 - (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hostBundle
    21  
    22 - (IBAction)relaunchNow:sender; 
    23 - (IBAction)relaunchLater:sender; 
     28- (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hostBundle delegate:delegate
     29- (IBAction)installNow:sender; 
     30- (IBAction)installLater:sender; 
     31- (IBAction)doNotInstall:sender; 
    2432 
    2533@end 
    2634 
     35@interface NSObject (SUAutomaticUpdateAlertDelegateProtocol) 
     36- (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)aua finishedWithChoice:(SUAutomaticInstallationChoice)choice; 
     37@end 
     38 
    2739#endif 
  • trunk/SUAutomaticUpdateAlert.m

    r132 r153  
    1212@implementation SUAutomaticUpdateAlert 
    1313 
    14 - (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hb
     14- (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hb delegate:del
    1515{ 
    1616        self = [super initWithHostBundle:hb windowNibName:@"SUAutomaticUpdateAlert"]; 
     
    1818        { 
    1919                updateItem = [item retain]; 
     20                delegate = del; 
    2021                hostBundle = [hb retain]; 
    2122                [self setShouldCascadeWindows:NO];       
     23                [[self window] center]; 
    2224        } 
    2325        return self; 
    2426} 
    2527 
    26 - (void) dealloc 
     28- (void)dealloc 
    2729{ 
    2830        [hostBundle release]; 
     
    3234 
    3335 
    34 - (IBAction)relaunchNow:sender 
     36- (IBAction)installNow:sender 
    3537{ 
    3638        [self close]; 
    37         [NSApp stopModalWithCode:NSAlertDefaultReturn]; 
     39        [delegate automaticUpdateAlert:self finishedWithChoice:SUInstallNowChoice]; 
    3840} 
    3941 
    40 - (IBAction)relaunchLater:sender 
     42- (IBAction)installLater:sender 
    4143{ 
    4244        [self close]; 
    43         [NSApp stopModalWithCode:NSAlertAlternateReturn]; 
     45        [delegate automaticUpdateAlert:self finishedWithChoice:SUInstallLaterChoice]; 
     46
     47 
     48- (IBAction)doNotInstall:sender 
     49
     50        [self close]; 
     51        [delegate automaticUpdateAlert:self finishedWithChoice:SUDoNotInstallChoice]; 
    4452} 
    4553 
     
    5159- (NSString *)titleText 
    5260{ 
    53         return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ has been installed!", nil), [hostBundle name]]; 
     61        return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is ready to install!", nil), [hostBundle name]]; 
    5462} 
    5563 
    5664- (NSString *)descriptionText 
    5765{ 
    58         return [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ has been installed and will be ready to use next time %1$@ starts! Would you like to relaunch now?", nil), [hostBundle name], [hostBundle displayVersion]]; 
     66        return [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?", nil), [hostBundle name], [hostBundle displayVersion]]; 
    5967} 
    6068 
  • trunk/SUConstants.h

    r147 r153  
    1313 
    1414extern NSString *SUUpdaterWillRestartNotification; 
     15extern NSString *SUTechnicalErrorInformationKey; 
    1516 
    1617extern NSString *SUFeedURLKey; 
     
    2930 
    3031extern NSString *SUSparkleErrorDomain; 
     32// Appcast phase errors. 
     33extern OSStatus SUAppcastParseError; 
     34extern OSStatus SUNoUpdateError; 
     35extern OSStatus SUAppcastError; 
     36extern OSStatus SURunningFromDiskImageError; 
     37 
     38// Downlaod phase errors. 
     39extern OSStatus SUTemporaryDirectoryError; 
     40 
     41// Extraction phase errors. 
     42extern OSStatus SUUnarchivingError; 
     43extern OSStatus SUSignatureError; 
     44 
     45// Installation phase errors. 
    3146extern OSStatus SUFileCopyFailure; 
    3247extern OSStatus SUAuthenticationFailure; 
    3348extern OSStatus SUMissingUpdateError; 
    3449extern OSStatus SUMissingInstallerToolError; 
     50extern OSStatus SURelaunchError; 
     51extern OSStatus SUInstallationError; 
    3552 
    3653// NSInteger is a type that was added to Leopard. 
  • trunk/SUConstants.m

    r147 r153  
    1111 
    1212NSString *SUUpdaterWillRestartNotification = @"SUUpdaterWillRestartNotificationName"; 
     13NSString *SUTechnicalErrorInformationKey = @"SUTechnicalErrorInformation"; 
    1314 
    1415NSString *SUHasLaunchedBeforeKey = @"SUHasLaunchedBefore"; 
     
    2728 
    2829NSString *SUSparkleErrorDomain = @"SUSparkleErrorDomain"; 
    29 OSStatus SUFileCopyFailure = 9000; 
    30 OSStatus SUAuthenticationFailure = 9001; 
    31 OSStatus SUMissingUpdateError = 9002; 
    32 OSStatus SUMissingInstallerToolError = 9003; 
     30OSStatus SUAppcastParseError = 1000; 
     31OSStatus SUNoUpdateError = 1001; 
     32OSStatus SUAppcastError = 1002; 
     33OSStatus SURunningFromDiskImageError = 1003; 
     34 
     35OSStatus SUTemporaryDirectoryError = 2000; 
     36 
     37OSStatus SUUnarchivingError = 3000; 
     38OSStatus SUSignatureError = 3001; 
     39 
     40OSStatus SUFileCopyFailure = 4000; 
     41OSStatus SUAuthenticationFailure = 4001; 
     42OSStatus SUMissingUpdateError = 4002; 
     43OSStatus SUMissingInstallerToolError = 4003; 
     44OSStatus SURelaunchError = 4004; 
     45OSStatus SUInstallationError = 4005; 
  • trunk/SUInstaller.h

    r147 r153  
    77// 
    88 
     9#ifndef SUINSTALLER_H 
     10#define SUINSTALLER_H 
     11 
    912#import <Cocoa/Cocoa.h> 
    1013 
    1114@interface SUInstaller : NSObject { } 
    12 + (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundle *)hostBundle delegate:delegate
     15+ (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundle *)hostBundle delegate:delegate synchronously:(BOOL)synchronously
    1316+ (void)_finishInstallationWithResult:(BOOL)result hostBundle:(NSBundle *)hostBundle error:(NSError *)error delegate:delegate; 
    1417@end 
    1518 
    1619@interface NSObject (SUInstallerDelegateInformalProtocol) 
    17 - installerFinishedForHostBundle:(NSBundle *)hostBundle; 
    18 - installerForHostBundle:(NSBundle *)hostBundle failedWithError:(NSError *)error; 
     20- (void)installerFinishedForHostBundle:(NSBundle *)hostBundle; 
     21- (void)installerForHostBundle:(NSBundle *)hostBundle failedWithError:(NSError *)error; 
    1922@end 
    2023 
    21 extern NSString *SUInstallerPathKey; 
    22 extern NSString *SUInstallerHostBundleKey; 
    23 extern NSString *SUInstallerDelegateKey; 
     24#endif 
  • trunk/SUInstaller.m

    r152 r153  
    1717@implementation SUInstaller 
    1818 
    19 + (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundle *)hostBundle delegate:delegate
     19+ (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundle *)hostBundle delegate:delegate synchronously:(BOOL)synchronously
    2020{ 
    2121        // Search subdirectories for the application 
     
    5757        else 
    5858        { 
    59                 NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:newAppDownloadPath, SUInstallerPathKey, hostBundle, SUInstallerHostBundleKey, delegate, SUInstallerDelegateKey, nil]; 
    60                 [NSThread detachNewThreadSelector:@selector(performInstallationWithInfo:) toTarget:(isPackage ? [SUPackageInstaller class] : [SUPlainInstaller class]) withObject:info]; 
     59