Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/quiet-automatic-update'
Browse files Browse the repository at this point in the history
* origin/quiet-automatic-update:
  Allow manual checks when an install on quit update is pending
  Allow the delegate to trigger a silent install and relaunch
  Support silent relaunches
  Increment the sudden termination counter if installing on quit
  Set up install on quit for critical automatic updates too
  Updating Sparkle.strings for the new, more strongly worded strings that are displayed for a 'critical update'
  Prompts the user to update after a week (rather than a day) if he doesn't quit the app.
  Adding <sparkle:tags> appcast item element, <sparkle:criticalUpdate /> tag
  Fixed a no-autorelease-pool leak.
  We have this check box that says "Automatically download and install updates in the future." But we only download them automatically. We still ask permission again before installing them.

Conflicts:
	SUBasicUpdateDriver.m
	en.lproj/Sparkle.strings
	finish_installation.m
  • Loading branch information
kornelski committed Feb 1, 2014
2 parents c99378c + 595c6ce commit d799325
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 16 deletions.
3 changes: 2 additions & 1 deletion SUAppcastItem.h
Expand Up @@ -29,7 +29,7 @@
NSDictionary *deltaUpdates;

NSDictionary *propertiesDictionary;

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

Expand All @@ -49,6 +49,7 @@
- (NSString *)maximumSystemVersion;
- (NSDictionary *)deltaUpdates;
- (BOOL)isDeltaUpdate;
- (BOOL)isCriticalUpdate;

// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions.
- (NSDictionary *)propertiesDictionary;
Expand Down
5 changes: 5 additions & 0 deletions SUAppcastItem.m
Expand Up @@ -138,6 +138,11 @@ - (BOOL)isDeltaUpdate
return [[propertiesDictionary objectForKey:@"enclosure"] objectForKey:@"sparkle:deltaFrom"] != nil;
}

- (BOOL)isCriticalUpdate
{
return [[propertiesDictionary objectForKey:@"sparkle:tags"] containsObject:@"sparkle:criticalUpdate"];
}

- initWithDictionary:(NSDictionary *)dict
{
return [self initWithDictionary:dict failureReason:nil];
Expand Down
18 changes: 16 additions & 2 deletions SUAutomaticUpdateAlert.m
Expand Up @@ -60,12 +60,26 @@ - (NSImage *)applicationIcon

- (NSString *)titleText
{
return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is ready to install!", nil), [host name]];
if ([updateItem isCriticalUpdate])
{
return [NSString stringWithFormat:SULocalizedString(@"An important update to %@ is ready to install", nil)];
}
else
{
return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is ready to install!", nil), [host name]];
}
}

- (NSString *)descriptionText
{
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), [host name], [updateItem displayVersionString]];
if ([updateItem isCriticalUpdate])
{
return [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ has been downloaded and is ready to use! This is an important update; would you like to install it and relaunch %1$@ now?", nil), [host name], [updateItem displayVersionString]];
}
else
{
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), [host name], [updateItem displayVersionString]];
}
}

@end
2 changes: 2 additions & 0 deletions SUAutomaticUpdateDriver.h
Expand Up @@ -17,7 +17,9 @@
{
@private
BOOL postponingInstallation, showErrors;
BOOL willUpdateOnTermination;
SUAutomaticUpdateAlert *alert;
NSTimer *showUpdateAlertTimer;
}

@end
Expand Down
95 changes: 91 additions & 4 deletions SUAutomaticUpdateDriver.m
Expand Up @@ -12,10 +12,14 @@
#import "SUHost.h"
#import "SUConstants.h"

// If the user hasn't quit in a week, ask them if they want to relaunch to get the latest bits. It doesn't matter that this measure of "one day" is imprecise.
static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 24 * 7;

@implementation SUAutomaticUpdateDriver

- (void)unarchiverDidFinish:(SUUnarchiver *)ua
- (void)showUpdateAlert
{
isInterruptible = NO;
alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:updateItem host:host delegate:self];

// If the app is a menubar app or the like, we need to focus it first and alter the
Expand All @@ -33,6 +37,83 @@ - (void)unarchiverDidFinish:(SUUnarchiver *)ua
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:NSApp];
}

- (void)unarchiverDidFinish:(SUUnarchiver *)ua
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];

// Sudden termination is available on 10.6+
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(disableSuddenTermination)]) {
[processInfo disableSuddenTermination];
}

willUpdateOnTermination = YES;

if ([[updater delegate] respondsToSelector:@selector(updater:willInstallUpdateOnQuit:immediateInstallationInvocation:)])
{
BOOL relaunch = YES;
BOOL showUI = NO;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(installWithToolAndRelaunch:displayingUserInterface:)]];
[invocation setSelector:@selector(installWithToolAndRelaunch:displayingUserInterface:)];
[invocation setArgument:&relaunch atIndex:2];
[invocation setArgument:&showUI atIndex:3];
[invocation setTarget:self];

[[updater delegate] updater:updater willInstallUpdateOnQuit:updateItem immediateInstallationInvocation:invocation];
}

// If this is marked as a critical update, we'll prompt the user to install it right away.
if ([updateItem isCriticalUpdate])
{
[self showUpdateAlert];
}
else
{
showUpdateAlertTimer = [[NSTimer scheduledTimerWithTimeInterval:SUAutomaticUpdatePromptImpatienceTimer target:self selector:@selector(showUpdateAlert) userInfo:nil repeats:NO] retain];

// At this point the driver is idle, allow it to be interrupted for user-initiated update checks.
isInterruptible = YES;
}
}

- (void)stopUpdatingOnTermination
{
if (willUpdateOnTermination)
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(enableSuddenTermination)]) {
[processInfo enableSuddenTermination];
}
willUpdateOnTermination = NO;

if ([[updater delegate] respondsToSelector:@selector(updater:didCancelInstallUpdateOnQuit:)])
[[updater delegate] updater:updater didCancelInstallUpdateOnQuit:updateItem];
}
}

- (void)invalidateShowUpdateAlertTimer
{
[showUpdateAlertTimer invalidate];
[showUpdateAlertTimer release];
showUpdateAlertTimer = nil;
}

- (void)dealloc
{
[self stopUpdatingOnTermination];
[self invalidateShowUpdateAlertTimer];
[alert release];
[super dealloc];
}

- (void)abortUpdate
{
[self stopUpdatingOnTermination];
[self invalidateShowUpdateAlertTimer];
[super abortUpdate];
}

- (void)applicationDidBecomeActive:(NSNotification *)aNotification
{
[[alert window] makeKeyAndOrderFront:self];
Expand All @@ -44,12 +125,15 @@ - (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)aua finishedWithChoice:(S
switch (choice)
{
case SUInstallNowChoice:
[self stopUpdatingOnTermination];
[self installWithToolAndRelaunch:YES];
break;

case SUInstallLaterChoice:
postponingInstallation = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
// We're already waiting on quit, just indicate that we're idle.
isInterruptible = YES;
break;

case SUDoNotInstallChoice:
Expand All @@ -61,10 +145,13 @@ - (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)aua finishedWithChoice:(S

- (BOOL)shouldInstallSynchronously { return postponingInstallation; }

- (void)installWithToolAndRelaunch:(BOOL)relaunch
- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI
{
showErrors = YES;
[super installWithToolAndRelaunch:relaunch];
if (relaunch)
[self stopUpdatingOnTermination];

showErrors = YES;
[super installWithToolAndRelaunch:relaunch displayingUserInterface:showUI];
}

- (void)applicationWillTerminate:(NSNotification *)note
Expand Down
1 change: 1 addition & 0 deletions SUBasicUpdateDriver.h
Expand Up @@ -47,6 +47,7 @@
- (void)failedToApplyDeltaUpdate;

- (void)installWithToolAndRelaunch:(BOOL)relaunch;
- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI;
- (void)installerForHost:(SUHost *)host failedWithError:(NSError *)error;

- (void)cleanUpDownload;
Expand Down
15 changes: 14 additions & 1 deletion SUBasicUpdateDriver.m
Expand Up @@ -299,6 +299,12 @@ - (void)unarchiverDidFail:(SUUnarchiver *)ua
- (BOOL)shouldInstallSynchronously { return NO; }

- (void)installWithToolAndRelaunch:(BOOL)relaunch
{
// Perhaps a poor assumption but: if we're not relaunching, we assume we shouldn't be showing any UI either. Because non-relaunching installations are kicked off without any user interaction, we shouldn't be interrupting them.
[self installWithToolAndRelaunch:relaunch displayingUserInterface:relaunch];
}

- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI
{
#if !ENDANGER_USERS_WITH_INSECURE_UPDATES
if (![self validateUpdateDownloadedToPath:downloadPath extractedToPath:tempDir DSASignature:[updateItem DSASignature] publicDSAKey:[host publicDSAKey]])
Expand Down Expand Up @@ -364,7 +370,14 @@ - (void)installWithToolAndRelaunch:(BOOL)relaunch
if ([[updater delegate] respondsToSelector:@selector(pathToRelaunchForUpdater:)])
pathToRelaunch = [[updater delegate] pathToRelaunchForUpdater:updater];
NSString *relaunchToolPath = [relaunchPath stringByAppendingPathComponent: @"/Contents/MacOS/finish_installation"];
[NSTask launchedTaskWithLaunchPath: relaunchToolPath arguments:[NSArray arrayWithObjects:[host bundlePath], pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], tempDir, relaunch ? @"1" : @"0", nil]];
[NSTask launchedTaskWithLaunchPath: relaunchToolPath arguments:[NSArray arrayWithObjects:
[host bundlePath],
pathToRelaunch,
[NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]],
tempDir,
relaunch ? @"1" : @"0",
showUI ? @"1" : @"0",
nil]];

[NSApp terminate:self];
}
Expand Down
2 changes: 2 additions & 0 deletions SUUpdateDriver.h
Expand Up @@ -21,11 +21,13 @@ extern NSString * const SUUpdateDriverFinishedNotification;
NSURL *appcastURL;

BOOL finished;
BOOL isInterruptible;
}

- initWithUpdater:(SUUpdater *)updater;
- (void)checkForUpdatesAtURL:(NSURL *)URL host:(SUHost *)host;
- (void)abortUpdate;
- (BOOL)isInterruptible;
- (BOOL)finished;
- (SUHost*)host;
- (void)setHost:(SUHost*)newHost;
Expand Down
2 changes: 2 additions & 0 deletions SUUpdateDriver.m
Expand Up @@ -33,6 +33,8 @@ - (void)abortUpdate
[[NSNotificationCenter defaultCenter] postNotificationName:SUUpdateDriverFinishedNotification object:self];
}

- (BOOL)isInterruptible { return isInterruptible; }

- (BOOL)finished { return finished; }

- (void)dealloc
Expand Down
5 changes: 5 additions & 0 deletions SUUpdater.h
Expand Up @@ -152,6 +152,11 @@ extern NSString *const SUUpdaterAppcastNotificationKey;
-(void) updaterWillShowModalAlert:(SUUpdater *)updater;
-(void) updaterDidShowModalAlert:(SUUpdater *)updater;

// Called when an update is scheduled to be silently installed on quit.
// The invocation can be used to trigger an immediate silent install and relaunch.
- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)update immediateInstallationInvocation:(NSInvocation *)invocation;
- (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)update;

@end


Expand Down
3 changes: 3 additions & 0 deletions SUUpdater.m
Expand Up @@ -302,6 +302,9 @@ - (BOOL)mayUpdateAndRestart

- (IBAction)checkForUpdates: (id)sender
{
if (driver && [driver isInterruptible])
[driver abortUpdate];

[self checkForUpdatesWithDriver:[[[SUUserInitiatedUpdateDriver alloc] initWithUpdater:self] autorelease]];
}

Expand Down
6 changes: 6 additions & 0 deletions en.lproj/Sparkle.strings
@@ -1,3 +1,6 @@
/* No comment provided by engineer. */
"%1$@ %2$@ has been downloaded and is ready to use! This is an important update; would you like to install it and relaunch %1$@ now?" = "%1$@ %2$@ has been downloaded and is ready to use! This is an important update; would you like to install it and relaunch %1$@ now?";

/* No comment provided by engineer. */
"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?" = "%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?";

Expand Down Expand Up @@ -40,6 +43,9 @@
/* No comment provided by engineer. */
"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@." = "An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.";

/* No comment provided by engineer. */
"An important update to %@ is ready to install" = "An important update to %@ is ready to install";

/* the unit for bytes */
"B" = "B";

Expand Down
21 changes: 13 additions & 8 deletions finish_installation.m
Expand Up @@ -24,6 +24,7 @@ @interface TerminationListener : NSObject
NSTimer *longInstallationTimer;
SUHost *host;
BOOL shouldRelaunch;
BOOL shouldShowUI;
}

- (void) parentHasQuit;
Expand All @@ -38,8 +39,7 @@ - (void) watchdog:(NSTimer *)aTimer;

@implementation TerminationListener

- (id) initWithHostPath:(const char *)inhostpath executablePath:(const char *)execpath parentProcessId:(pid_t)ppid folderPath: (const char*)infolderpath shouldRelaunch:(BOOL)relaunch
selfPath: (NSString*)inSelfPath
- (id) initWithHostPath:(const char *)inhostpath executablePath:(const char *)execpath parentProcessId:(pid_t)ppid folderPath:(const char*)infolderpath shouldRelaunch:(BOOL)relaunch shouldShowUI:(BOOL)showUI selfPath:(NSString*)inSelfPath
{
if( !(self = [super init]) )
return nil;
Expand All @@ -50,6 +50,7 @@ - (id) initWithHostPath:(const char *)inhostpath executablePath:(const char *)ex
folderpath = infolderpath;
selfPath = [inSelfPath retain];
shouldRelaunch = relaunch;
shouldShowUI = showUI;

BOOL alreadyTerminated = (getppid() == 1); // ppid is launchd (1) => parent terminated already

Expand Down Expand Up @@ -144,8 +145,7 @@ - (void) install
host = [[SUHost alloc] initWithBundle: theBundle];
installationPath = [[host installationPath] copy];

// Perhaps a poor assumption but: if we're not relaunching, we assume we shouldn't be showing any UI either. Because non-relaunching installations are kicked off without any user interaction, we shouldn't be interrupting them.
if (shouldRelaunch) {
if (shouldShowUI) {
SUStatusController* statusCtl = [[SUStatusController alloc] initWithHost: host]; // We quit anyway after we've installed, so leak this for now.
[statusCtl setButtonTitle: SULocalizedString(@"Cancel Update",@"") target: nil action: Nil isDefault: NO];
[statusCtl beginActionWithTitle: SULocalizedString(@"Installing update...",@"")
Expand All @@ -168,8 +168,7 @@ - (void) installerFinishedForHost:(SUHost *)aHost

- (void) installerForHost:(SUHost *)host failedWithError:(NSError *)error
{
// Perhaps a poor assumption but: if we're not relaunching, we assume we shouldn't be showing any UI either. Because non-relaunching installations are kicked off without any user interaction, we shouldn't be interrupting them.
if (shouldRelaunch)
if (shouldShowUI)
NSRunAlertPanel( @"", @"%@", @"OK", @"", @"", [error localizedDescription] );
exit(EXIT_FAILURE);
}
Expand All @@ -178,14 +177,13 @@ - (void) installerForHost:(SUHost *)host failedWithError:(NSError *)error

int main (int argc, const char * argv[])
{
if( argc < 5 || argc > 6 )
if( argc < 5 || argc > 7 )
return EXIT_FAILURE;

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

//ProcessSerialNumber psn = { 0, kCurrentProcess };
//TransformProcessType( &psn, kProcessTransformToForegroundApplication );
[[NSApplication sharedApplication] activateIgnoringOtherApps: YES];

#if 0 // Cmdline tool
NSString* selfPath = nil;
Expand All @@ -200,12 +198,19 @@ int main (int argc, const char * argv[])
NSString* selfPath = [[NSBundle mainBundle] bundlePath];
#endif

BOOL shouldShowUI = (argc > 6) ? atoi(argv[6]) : 1;
if (shouldShowUI)
{
[[NSApplication sharedApplication] activateIgnoringOtherApps: YES];
}

[NSApplication sharedApplication];
[[[TerminationListener alloc] initWithHostPath: (argc > 1) ? argv[1] : NULL
executablePath: (argc > 2) ? argv[2] : NULL
parentProcessId: (argc > 3) ? atoi(argv[3]) : 0
folderPath: (argc > 4) ? argv[4] : NULL
shouldRelaunch: (argc > 5) ? atoi(argv[5]) : 1
shouldShowUI: shouldShowUI
selfPath: selfPath] autorelease];
[[NSApplication sharedApplication] run];

Expand Down

0 comments on commit d799325

Please sign in to comment.