Skip to content

Commit

Permalink
Merge of changes from SVN repository:
Browse files Browse the repository at this point in the history
- Changed NTSynchronousTask to also give the status return value and direct stderror output to the outputData.
- Changed includes so this builds as part of an app, too, not just as a framework
- Made sure SUAppcast's dealloc releases some leaked ivars.
- Added infoURL, extracted from link, that can point to a "more Info" page for download-less URLs
- Added support for version attribute on item so we can support update notifications that don't include an enclosure (e.g. paid upgrades, or upgrades that would require a system update)
- Added/improved a few description methods to ease debugging.
- Added SULog so one can ask for a special log with additional information when there are update issues.
- Added mayUpdateAndRestart for apps that absolutely, positively can't restart right now (e.g. cuz they're burning a CD and would produce a coaster).
- Added updaterWillRelaunchApplication delegate method, analogous to the notification. Useful to have app delegate quit helper apps during installation.
- Made SUBasicUpdateDriver's abortUpdate implicitly retain/autorelease the update driver, because the notification center otherwise releases it and it goes away, causing crashes in superclass's abortUpdate.
- Merge of SUKeepDownloadOnFailedInstallKey and SUFixedHTMLDisplaySizeKey.
- Avoid a few warnings about missing prototypes
- Be paranoid, hdiutil can verify the download again, so let it. Better for internal apps where we turn off DSA checks, too.
- SUHost has an -installationPath now, independent from the bundlePath, so one can normalize the app name from "MyApp 1.1b4" back to "MyApp" Users assume the file name contains the correct version number when there is one in it. Saves support a few round-trips each time.
- Be better at threading: Try calling non-thread-safe methods on main thread only, and don't assume delegates know when they need to be thread-safe, call them on main thread where possible.
- Added a method to put the old copy of the app in the trash. 1.5git changed in this spot, so I didn't actually merge the code that uses it back in yet.
- Fix version comparison so it doesn't get confused by bracketed build numbers in version strings
- Make sure cancel button is disabled during extraction, otherwise user would crash.
- Don't put auto-update window at floating window level. It's huge and can't be switched to background! If you're an NSBGOnly where you need that, turn it on only in that case, but don't generally do such nonsense.
- Hide release notes view if there aren't any.
- Test whether we are on dial-up before checking for updates in background. It's not nice to cause (possibly expensive) dial-up periodically.
- Temporarily comment out DSA complaints for easier testing.
- Don't store (possibly already invalidated) one-shot NSTimers in an ivar. It's bad style. Retain it instead.
- Decompress some monster expressions with nested method calls in ternary operators and nested in method calls again.
- Don't use implicit "id" for params or return types.
- finish_installation now puts up a progress window, so user knows update is still not finished.
- Use ThreadSafePreferences (included dummy version that uses regular prefs for projects that don't use ThreadSafePreferences).

- Todo later: Change finish_installation to be prettier.
  • Loading branch information
uliwitness committed Dec 4, 2009
1 parent bbcfe7e commit 43a0a7d
Show file tree
Hide file tree
Showing 100 changed files with 1,985 additions and 397 deletions.
15 changes: 15 additions & 0 deletions Elgato/ThreadSafePreferences.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Header that shouldn't be included in anything but the Sparkle finish_installation tool
// because it includes files also used in EyeTV, but doesn't need to be thread safe
// as it's a single-threaded process anyway.

#if !EYETV && !__TOAST__ && !TURBO

#define ThreadSafePreferences_CopyAppValue CFPreferencesCopyAppValue
#define ThreadSafePreferences_SetValue CFPreferencesSetValue
#define ThreadSafePreferences_Synchronize CFPreferencesSynchronize

#else

#error This header shouldn't be included here!

#endif
2 changes: 1 addition & 1 deletion NTSynchronousTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

// pass nil for directory if not needed
// returns the result
+ (NSData*)task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input;
+(int) task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input output: (NSData**)outData;

@end

Expand Down
26 changes: 18 additions & 8 deletions NTSynchronousTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
// Copyright 2005 Steve Gehrman. All rights reserved.
//

#import "Sparkle.h"
#import "SUUpdater.h"

#import "SUAppcast.h"
#import "SUAppcastItem.h"
#import "SUVersionComparisonProtocol.h"
#import "NTSynchronousTask.h"

@interface NTSynchronousTask (Private)
Expand Down Expand Up @@ -44,6 +48,7 @@ - (id)init;

[[self task] setStandardInput:[self inputPipe]];
[[self task] setStandardOutput:[self outputPipe]];
[[self task] setStandardError:[self outputPipe]];
}

return self;
Expand All @@ -64,32 +69,37 @@ - (void)dealloc
[super dealloc];
}

+ (NSData*)task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input;
+(int) task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input output: (NSData**)outData
{
// we need this wacky pool here, otherwise we run out of pipes, the pipes are internally autoreleased
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData* result=nil;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int taskResult = 0;
if( outData )
*outData = nil;

NS_DURING
{
NTSynchronousTask* task = [[NTSynchronousTask alloc] init];

[task run:toolPath directory:currentDirectory withArgs:args input:input];

if ([task result] == 0)
result = [[task output] retain];
taskResult = [task result];
if( outData )
*outData = [[task output] retain];

[task release];
}
NS_HANDLER;
taskResult = errCppGeneral;
NS_ENDHANDLER;

[pool drain];

// retained above
[result autorelease];
if( outData )
[*outData autorelease];

return result;
return taskResult;
}

@end
Expand Down
20 changes: 19 additions & 1 deletion SUAppcast.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//

#import "Sparkle.h"
#import "SUUpdater.h"

#import "SUAppcast.h"
#import "SUAppcastItem.h"
#import "SUVersionComparisonProtocol.h"
#import "SUAppcast.h"
#import "SUConstants.h"
#import "SULog.h"


@interface SUAppcast (Private)
- (void)reportError:(NSError *)error;
Expand All @@ -19,7 +26,12 @@ @implementation SUAppcast
- (void)dealloc
{
[items release];
items = nil;
[userAgentString release];
userAgentString = nil;
[downloadFilename release];
downloadFilename = nil;

[super dealloc];
}

Expand Down Expand Up @@ -59,6 +71,12 @@ - (void)downloadDidFinish:(NSURLDownload *)download
BOOL failed = NO;
NSArray *xmlItems = nil;
NSMutableArray *appcastItems = [NSMutableArray array];

#if DEBUG
NSString* debugXML = [NSString stringWithContentsOfFile: downloadFilename encoding: NSUTF8StringEncoding error: nil];
SULog(@"<<<< XML >>>>\n%@\n>>>> XML <<<<", debugXML);
#endif

#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
[[NSFileManager defaultManager] removeFileAtPath:downloadFilename handler:nil];
#else
Expand Down
4 changes: 4 additions & 0 deletions SUAppcastItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
NSString *displayVersionString;

NSDictionary *propertiesDictionary;

NSURL *infoURL; // UK 2007-08-31
}

// Initializes with data from a dictionary provided by the RSS class.
Expand All @@ -43,6 +45,8 @@
// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions.
- (NSDictionary *)propertiesDictionary;

- (NSURL *)infoURL; // UK 2007-08-31

@end

#endif
32 changes: 29 additions & 3 deletions SUAppcastItem.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//

#import "Sparkle.h"
#import "SUUpdater.h"

#import "SUAppcast.h"
#import "SUAppcastItem.h"
#import "SUVersionComparisonProtocol.h"
#import "SUAppcastItem.h"

@implementation SUAppcastItem
Expand Down Expand Up @@ -101,6 +105,16 @@ - (void)setMinimumSystemVersion:(NSString *)systemVersionString
minimumSystemVersion = [systemVersionString copy];
}


- (NSURL *)infoURL { return [[infoURL retain] autorelease]; } // UK 2007-08-31 (whole method)

- (void)setInfoURL:(NSURL *)aFileURL // UK 2007-08-31 (whole method)
{
if( aFileURL == infoURL ) return;
[infoURL release];
infoURL = [aFileURL copy];
}

- initWithDictionary:(NSDictionary *)dict
{
return [self initWithDictionary:dict failureReason:nil];
Expand Down Expand Up @@ -130,7 +144,9 @@ - (void)setMinimumSystemVersion:(NSString *)systemVersionString
// The big caveat with this is that you can't have underscores in your version strings, as that'll confuse Sparkle.
// Feel free to change the separator string to a hyphen or something more suited to your needs if you like.
NSString *newVersion = [enclosure objectForKey:@"sparkle:version"];
if (newVersion == nil) // no sparkle:version attribute
if( newVersion == nil )
newVersion = [dict objectForKey:@"sparkle:version"]; // UK 2007-08-31 Get version from the item, in case it's a download-less item (i.e. paid upgrade).
if (newVersion == nil) // no sparkle:version attribute anywhere?
{
// Separate the url by underscores and take the last component, as that'll be closest to the end,
// then we remove the extension. Hopefully, this will be the version.
Expand Down Expand Up @@ -160,7 +176,16 @@ - (void)setMinimumSystemVersion:(NSString *)systemVersionString
[self setDate:[dict objectForKey:@"pubDate"]];
[self setItemDescription:[dict objectForKey:@"description"]];

[self setFileURL:[NSURL URLWithString:[[enclosure objectForKey:@"url"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
NSString* theInfoURL = [dict objectForKey:@"link"];
if( theInfoURL )
{
if( ![theInfoURL isKindOfClass: [NSString class]] )
NSLog(@"SUAppcastItem -initWithDictionary: Info URL is not of valid type.");
else
[self setInfoURL:[NSURL URLWithString:theInfoURL]];
}

[self setFileURL:[NSURL URLWithString:[[enclosure objectForKey:@"url"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
[self setDSASignature:[enclosure objectForKey:@"sparkle:dsaSignature"]];

[self setVersionString:newVersion];
Expand Down Expand Up @@ -193,6 +218,7 @@ - (void)dealloc
[self setFileURL:nil];
[self setVersionString:nil];
[self setDisplayVersionString:nil];
[self setInfoURL:nil];
[propertiesDictionary release];
[super dealloc];
}
Expand Down
2 changes: 1 addition & 1 deletion SUAutomaticUpdateAlert.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ - (void)dealloc
[super dealloc];
}

- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [host bundlePath]]; }
- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [host bundlePath], [host installationPath]]; }

- (IBAction)installNow:sender
{
Expand Down
1 change: 1 addition & 0 deletions SUAutomaticUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#import "SUAutomaticUpdateAlert.h"
#import "SUHost.h"
#import "SUConstants.h"

@implementation SUAutomaticUpdateDriver

Expand Down
80 changes: 49 additions & 31 deletions SUBasicUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#import "SUInstaller.h"
#import "SUStandardVersionComparator.h"
#import "SUUnarchiver.h"
#import "SUConstants.h"
#import "SULog.h"


@implementation SUBasicUpdateDriver

Expand Down Expand Up @@ -165,6 +168,7 @@ - (void)download:(NSURLDownload *)d decideDestinationWithSuggestedFilename:(NSSt

- (void)downloadDidFinish:(NSURLDownload *)d
{
#if 0 // +++
// New in Sparkle 1.5: we're now checking signatures on all non-secure downloads, where "secure" is defined as both the appcast and the download being transmitted over SSL.
NSURL *downloadURL = [[d request] URL];
if (!(([[downloadURL scheme] isEqualToString:@"https"] && [[appcastURL scheme] isEqualToString:@"https"]) ||
Expand All @@ -176,6 +180,7 @@ - (void)downloadDidFinish:(NSURLDownload *)d
return;
}
}
#endif

[self extractUpdate];
}
Expand All @@ -200,7 +205,7 @@ - (void)extractUpdate
SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForPath:downloadPath];
if (!unarchiver)
{
NSLog(@"Sparkle Error: No valid unarchiver for %@!", downloadPath);
SULog(@"Sparkle Error: No valid unarchiver for %@!", downloadPath);
[self unarchiverDidFail:nil];
return;
}
Expand Down Expand Up @@ -231,7 +236,11 @@ - (void)installUpdate
NSString *relaunchPathToCopy = [[NSBundle bundleForClass:[self class]] pathForResource:@"finish_installation" ofType:@""];
NSString *appSupportFolder = [[@"~/Library/Application Support/" stringByExpandingTildeInPath] stringByAppendingPathComponent: [host name]];
NSString *targetPath = [appSupportFolder stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
[[NSFileManager defaultManager] createDirectoryAtPath: targetPath withIntermediateDirectories: YES attributes: [NSDictionary dictionary] error: NULL];
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
[[NSFileManager defaultManager] createDirectoryAtPath: [targetPath stringByDeletingLastPathComponent] attributes: [NSDictionary dictionary]];
#else
[[NSFileManager defaultManager] createDirectoryAtPath: [targetPath stringByDeletingLastPathComponent] withIntermediateDirectories: YES attributes: [NSDictionary dictionary] error: NULL];
#endif

// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
NSError *error = nil;
Expand All @@ -251,36 +260,43 @@ - (void)installUpdate

- (void)installAndRelaunchWithTool
{
// Give the host app an opportunity to postpone the relaunch.
static BOOL postponedOnce = NO;
if (!postponedOnce && [[updater delegate] respondsToSelector:@selector(updater:shouldPostponeRelaunchForUpdate:untilInvoking:)])
BOOL mayRelaunchAtAll = [updater mayUpdateAndRestart];

if( mayRelaunchAtAll )
{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(relaunchHostApp)]];
[invocation setSelector:@selector(relaunchHostApp)];
[invocation setTarget:self];
postponedOnce = YES;
if ([[updater delegate] updater:updater shouldPostponeRelaunchForUpdate:updateItem untilInvoking:invocation])
return;
}

[[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
if ([[updater delegate] respondsToSelector:@selector(updaterWillRelaunchApplication:)])
[[updater delegate] updaterWillRelaunchApplication:updater];
// Give the host app an opportunity to postpone the relaunch.
static BOOL postponedOnce = NO;
if (!postponedOnce && [[updater delegate] respondsToSelector:@selector(updater:shouldPostponeRelaunchForUpdate:untilInvoking:)])
{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(relaunchHostApp)]];
[invocation setSelector:@selector(relaunchHostApp)];
[invocation setTarget:self];
postponedOnce = YES;
if ([[updater delegate] updater:updater shouldPostponeRelaunchForUpdate:updateItem untilInvoking:invocation])
return;
}

if(!relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:relaunchPath])
{
// Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [host name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]];
// We intentionally don't abandon the update here so that the host won't initiate another.
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
if ([[updater delegate] respondsToSelector:@selector(updaterWillRelaunchApplication:)])
[[updater delegate] updaterWillRelaunchApplication:updater];

NSString *pathToRelaunch = [host bundlePath];
if ([[updater delegate] respondsToSelector:@selector(pathToRelaunchForUpdater:)])
pathToRelaunch = [[updater delegate] pathToRelaunchForUpdater:updater];
[NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], [downloadPath stringByDeletingLastPathComponent], nil]];

[NSApp terminate:self];
if(!relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:relaunchPath])
{
// Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [host name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]];
// We intentionally don't abandon the update here so that the host won't initiate another.
return;
}

NSString *pathToRelaunch = [host bundlePath];
if ([[updater delegate] respondsToSelector:@selector(pathToRelaunchForUpdater:)])
pathToRelaunch = [[updater delegate] pathToRelaunchForUpdater:updater];
[NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], [downloadPath stringByDeletingLastPathComponent], nil]];

[NSApp terminate:self];
}
else
[self abortUpdate];
}

- (void)cleanUp
Expand All @@ -295,21 +311,23 @@ - (void)cleanUp
- (void)installerForHost:(SUHost *)aHost failedWithError:(NSError *)error
{
if (aHost != host) { return; }
[[NSFileManager defaultManager] removeFileAtPath:relaunchPath handler:NULL]; // Clean up the copied relauncher.
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]];
}

- (void)abortUpdate
{
[[self retain] autorelease]; // In case the notification center was the last one holding on to us.
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super abortUpdate];
}

- (void)abortUpdateWithError:(NSError *)error
{
if ([error code] != SUNoUpdateError) // Let's not bother logging this.
NSLog(@"Sparkle Error: %@", [error localizedDescription]);
SULog(@"Sparkle Error: %@", [error localizedDescription]);
if ([error localizedFailureReason])
NSLog(@"Sparkle Error (continued): %@", [error localizedFailureReason]);
SULog(@"Sparkle Error (continued): %@", [error localizedFailureReason]);
if (download)
[download cancel];
[self abortUpdate];
Expand Down
Loading

0 comments on commit 43a0a7d

Please sign in to comment.