/
SUAutomaticUpdateDriver.m
231 lines (187 loc) · 7.67 KB
/
SUAutomaticUpdateDriver.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//
// SUAutomaticUpdateDriver.m
// Sparkle
//
// Created by Andy Matuschak on 5/6/08.
// Copyright 2008 Andy Matuschak. All rights reserved.
//
#import "SUAutomaticUpdateDriver.h"
#import "SUUpdaterPrivate.h"
#import "SUUpdaterDelegate.h"
#import "SULocalizations.h"
#import "SUErrors.h"
#import "SUAutomaticUpdateAlert.h"
#import "SUHost.h"
#import "SUConstants.h"
#import "SUAppcastItem.h"
#import "SUApplicationInfo.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 willUpdateOnTermination;
@property (assign) BOOL isTerminating;
@property (strong) SUAutomaticUpdateAlert *alert;
@property (strong) NSTimer *showUpdateAlertTimer;
@end
@implementation SUAutomaticUpdateDriver
@synthesize postponingInstallation;
@synthesize willUpdateOnTermination;
@synthesize isTerminating;
@synthesize alert;
@synthesize showUpdateAlertTimer;
- (void)showUpdateAlert
{
self.interruptible = NO;
[self invalidateShowUpdateAlertTimer];
if (self.alert) {
[self.alert close];
}
self.alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host completionBlock:^(SUAutomaticInstallationChoice choice) {
[self automaticUpdateAlertFinishedWithChoice:choice];
}];
// 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 ([SUApplicationInfo isBackgroundApplication:[NSApplication sharedApplication]]) {
[[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:(id)__unused ua
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(systemWillPowerOff:) name:NSWorkspaceWillPowerOffNotification object:nil];
// Sudden termination is available on 10.6+
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
[processInfo disableSuddenTermination];
self.willUpdateOnTermination = YES;
id<SUUpdaterPrivate> updater = self.updater;
id<SUUpdaterDelegate> updaterDelegate = [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;
}
}
-(BOOL)resumeUpdateInteractively {
if ([self isInterruptible]) {
[self showUpdateAlert];
return YES;
}
return NO;
}
-(BOOL)downloadsUpdatesInBackground {
return YES;
}
- (void)stopUpdatingOnTermination
{
if (self.willUpdateOnTermination)
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self name:NSWorkspaceWillPowerOffNotification object:nil];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
[processInfo enableSuddenTermination];
self.willUpdateOnTermination = NO;
id<SUUpdaterPrivate> updater = self.updater;
id<SUUpdaterDelegate> updaterDelegate = [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.alert close];
self.isTerminating = NO;
[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)automaticUpdateAlertFinishedWithChoice:(SUAutomaticInstallationChoice)choice
{
self.alert = nil;
switch (choice)
{
case SUInstallNowChoice:
[self stopUpdatingOnTermination];
[self installWithToolAndRelaunch:YES];
break;
case SUInstallLaterChoice:
self.postponingInstallation = YES;
// 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;
}
}
- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI
{
if (relaunch) {
[self stopUpdatingOnTermination];
}
[super installWithToolAndRelaunch:relaunch displayingUserInterface:showUI];
}
- (void)systemWillPowerOff:(NSNotification *)__unused note
{
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUSystemPowerOffError userInfo:@{
NSLocalizedDescriptionKey: SULocalizedString(@"The update will not be installed because the user requested for the system to power off", nil)
}]];
}
- (void)applicationWillTerminate:(NSNotification *)__unused note
{
// We don't want to terminate the app if the user or someone else initiated a termination
// Use a property instead of passing an argument to installWithToolAndRelaunch:
// because we give the delegate an invocation to our install methods and
// this code was added later :|
self.isTerminating = YES;
[self installWithToolAndRelaunch:NO];
}
- (void)terminateApp
{
if (!self.isTerminating) {
[super terminateApp];
}
}
@end