Cog/Frameworks/Sparkle/Sparkle/SUAutomaticUpdateDriver.m
2014-09-03 09:47:40 -07:00

189 lines
6.3 KiB
Objective-C

//
// SUAutomaticUpdateDriver.m
// Sparkle
//
// Created by Andy Matuschak on 5/6/08.
// Copyright 2008 Andy Matuschak. All rights reserved.
//
#import "SUAutomaticUpdateDriver.h"
#import "SUAutomaticUpdateAlert.h"
#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;
@interface SUUpdateDriver ()
@property (getter=isInterruptible) BOOL interruptible;
@end
@interface SUAutomaticUpdateDriver ()
@property (assign) BOOL postponingInstallation;
@property (assign) BOOL showErrors;
@property (assign) BOOL willUpdateOnTermination;
@property (strong) SUAutomaticUpdateAlert *alert;
@property (strong) NSTimer *showUpdateAlertTimer;
@end
@implementation SUAutomaticUpdateDriver
@synthesize postponingInstallation;
@synthesize showErrors;
@synthesize willUpdateOnTermination;
@synthesize alert;
@synthesize showUpdateAlertTimer;
- (void)showUpdateAlert
{
self.interruptible = NO;
self.alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host delegate:self];
// If the app is a menubar app or the like, we need to focus it first and alter the
// update prompt to behave like a normal window. Otherwise if the window were hidden
// there may be no way for the application to be activated to make it visible again.
if ([self.host isBackgroundApplication])
{
[[self.alert window] setHidesOnDeactivate:NO];
[NSApp activateIgnoringOtherApps:YES];
}
if ([NSApp isActive])
[[self.alert window] makeKeyAndOrderFront:self];
else
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
- (void)unarchiverDidFinish:(SUUnarchiver *)__unused ua
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
// Sudden termination is available on 10.6+
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
[processInfo disableSuddenTermination];
self.willUpdateOnTermination = YES;
id<SUUpdaterDelegate> updaterDelegate = [self.updater delegate];
if ([updaterDelegate 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];
[updaterDelegate updater:self.updater willInstallUpdateOnQuit:self.updateItem immediateInstallationInvocation:invocation];
}
// If this is marked as a critical update, we'll prompt the user to install it right away.
if ([self.updateItem isCriticalUpdate])
{
[self showUpdateAlert];
}
else
{
self.showUpdateAlertTimer = [NSTimer scheduledTimerWithTimeInterval:SUAutomaticUpdatePromptImpatienceTimer target:self selector:@selector(showUpdateAlert) userInfo:nil repeats:NO];
// At this point the driver is idle, allow it to be interrupted for user-initiated update checks.
self.interruptible = YES;
}
}
- (void)stopUpdatingOnTermination
{
if (self.willUpdateOnTermination)
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
[processInfo enableSuddenTermination];
self.willUpdateOnTermination = NO;
id<SUUpdaterDelegate> updaterDelegate = [self.updater delegate];
if ([updaterDelegate respondsToSelector:@selector(updater:didCancelInstallUpdateOnQuit:)])
[updaterDelegate updater:self.updater didCancelInstallUpdateOnQuit:self.updateItem];
}
}
- (void)invalidateShowUpdateAlertTimer
{
[self.showUpdateAlertTimer invalidate];
self.showUpdateAlertTimer = nil;
}
- (void)dealloc
{
[self stopUpdatingOnTermination];
[self invalidateShowUpdateAlertTimer];
}
- (void)abortUpdate
{
[self stopUpdatingOnTermination];
[self invalidateShowUpdateAlertTimer];
[super abortUpdate];
}
- (void)applicationDidBecomeActive:(NSNotification *)__unused aNotification
{
[[self.alert window] makeKeyAndOrderFront:self];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
- (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)__unused aua finishedWithChoice:(SUAutomaticInstallationChoice)choice
{
switch (choice)
{
case SUInstallNowChoice:
[self stopUpdatingOnTermination];
[self installWithToolAndRelaunch:YES];
break;
case SUInstallLaterChoice:
self.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.
self.interruptible = YES;
break;
case SUDoNotInstallChoice:
[self.host setObject:[self.updateItem versionString] forUserDefaultsKey:SUSkippedVersionKey];
[self abortUpdate];
break;
}
}
- (BOOL)shouldInstallSynchronously { return self.postponingInstallation; }
- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI
{
if (relaunch) {
[self stopUpdatingOnTermination];
}
self.showErrors = YES;
[super installWithToolAndRelaunch:relaunch displayingUserInterface:showUI];
}
- (void)applicationWillTerminate:(NSNotification *)__unused note
{
[self installWithToolAndRelaunch:NO];
}
- (void)abortUpdateWithError:(NSError *)error
{
if (self.showErrors)
[super abortUpdateWithError:error];
else
[self abortUpdate];
}
@end