forked from sparkle-project/Sparkle
-
Notifications
You must be signed in to change notification settings - Fork 4
/
SUUIBasedUpdateDriver.m
236 lines (198 loc) · 8.75 KB
/
SUUIBasedUpdateDriver.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
232
233
234
235
236
//
// SUUIBasedUpdateDriver.m
// Sparkle
//
// Created by Andy Matuschak on 5/5/08.
// Copyright 2008 Andy Matuschak. All rights reserved.
//
#import "SUUIBasedUpdateDriver.h"
#import "SUUpdateAlert.h"
#import "SUUpdater_Private.h"
#import "SUHost.h"
#import "SUStatusController.h"
#import "SUConstants.h"
#import "SUPasswordPrompt.h"
@implementation SUUIBasedUpdateDriver
- (void)didFindValidUpdate
{
updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem host:host];
[updateAlert setDelegate:self];
id<SUVersionDisplay> versDisp = nil;
if ([[updater delegate] respondsToSelector:@selector(versionDisplayerForUpdater:)])
versDisp = [[updater delegate] versionDisplayerForUpdater: updater];
[updateAlert setVersionDisplayer: versDisp];
if ([[updater delegate] respondsToSelector:@selector(updater:didFindValidUpdate:)])
[[updater delegate] updater:updater didFindValidUpdate:updateItem];
// 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 ([host isBackgroundApplication])
{
[[updateAlert window] setHidesOnDeactivate:NO];
[NSApp activateIgnoringOtherApps:YES];
}
// Only show the update alert if the app is active; otherwise, we'll wait until it is.
if ([NSApp isActive])
[[updateAlert window] makeKeyAndOrderFront:self];
else
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
- (void)didNotFindUpdate
{
if ([[updater delegate] respondsToSelector:@selector(updaterDidNotFindUpdate:)])
[[updater delegate] updaterDidNotFindUpdate:updater];
NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"You're up-to-date!", nil) defaultButton:SULocalizedString(@"OK", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), [host name], [host displayVersion]];
[self showModalAlert:alert];
[self abortUpdate];
}
- (void)applicationDidBecomeActive:(NSNotification *)aNotification
{
[[updateAlert window] makeKeyAndOrderFront:self];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"NSApplicationDidBecomeActiveNotification" object:NSApp];
}
- (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoice)choice
{
[updateAlert release]; updateAlert = nil;
[host setObject:nil forUserDefaultsKey:SUSkippedVersionKey];
switch (choice)
{
case SUInstallUpdateChoice:
statusController = [[SUStatusController alloc] initWithHost:host];
[statusController beginActionWithTitle:SULocalizedString(@"Downloading update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
[statusController setButtonTitle:SULocalizedString(@"Cancel", nil) target:self action:@selector(cancelDownload:) isDefault:NO];
[statusController showWindow:self];
[self downloadUpdate];
break;
case SUOpenInfoURLChoice:
[[NSWorkspace sharedWorkspace] openURL: [updateItem infoURL]];
[self abortUpdate];
break;
case SUSkipThisVersionChoice:
[host setObject:[updateItem versionString] forUserDefaultsKey:SUSkippedVersionKey];
[self abortUpdate];
break;
case SURemindMeLaterChoice:
[self abortUpdate];
break;
}
}
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
{
[statusController setMaxProgressValue:[response expectedContentLength]];
}
- (NSString *)humanReadableSizeFromDouble:(double)value
{
if (value < 1000)
return [NSString stringWithFormat:@"%.0lf %@", value, SULocalizedString(@"B", @"the unit for bytes")];
if (value < 1000 * 1000)
return [NSString stringWithFormat:@"%.0lf %@", value / 1000.0, SULocalizedString(@"KB", @"the unit for kilobytes")];
if (value < 1000 * 1000 * 1000)
return [NSString stringWithFormat:@"%.1lf %@", value / 1000.0 / 1000.0, SULocalizedString(@"MB", @"the unit for megabytes")];
return [NSString stringWithFormat:@"%.2lf %@", value / 1000.0 / 1000.0 / 1000.0, SULocalizedString(@"GB", @"the unit for gigabytes")];
}
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(NSUInteger)length
{
[statusController setProgressValue:[statusController progressValue] + (double)length];
if ([statusController maxProgressValue] > 0.0)
[statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%@ of %@", nil), [self humanReadableSizeFromDouble:[statusController progressValue]], [self humanReadableSizeFromDouble:[statusController maxProgressValue]]]];
else
[statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%@ downloaded", nil), [self humanReadableSizeFromDouble:[statusController progressValue]]]];
}
- (IBAction)cancelDownload: (id)sender
{
if (download)
[download cancel];
[self abortUpdate];
}
- (void)extractUpdate
{
// Now we have to extract the downloaded archive.
[statusController beginActionWithTitle:SULocalizedString(@"Extracting update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
[statusController setButtonEnabled:NO];
[super extractUpdate];
}
- (void)unarchiver:(SUUnarchiver *)ua extractedLength:(unsigned long)length
{
// We do this here instead of in extractUpdate so that we only have a determinate progress bar for archives with progress.
if ([statusController maxProgressValue] == 0.0)
{
NSDictionary * attributes;
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
attributes = [[NSFileManager defaultManager] fileAttributesAtPath:downloadPath traverseLink:NO];
#else
attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:downloadPath error:nil];
#endif
[statusController setMaxProgressValue:[[attributes objectForKey:NSFileSize] doubleValue]];
}
[statusController setProgressValue:[statusController progressValue] + (double)length];
}
- (void)unarchiverDidFinish:(SUUnarchiver *)ua
{
[statusController beginActionWithTitle:SULocalizedString(@"Ready to Install", nil) maxProgressValue:1.0 statusText:nil];
[statusController setProgressValue:1.0]; // Fill the bar.
[statusController setButtonEnabled:YES];
[statusController setButtonTitle:SULocalizedString(@"Install and Relaunch", nil) target:self action:@selector(installAndRestart:) isDefault:YES];
[[statusController window] makeKeyAndOrderFront: self];
[NSApp requestUserAttention:NSInformationalRequest];
}
- (void)unarchiver:(SUUnarchiver *)unarchiver requiresPasswordReturnedViaInvocation:(NSInvocation *)invocation
{
SUPasswordPrompt *prompt = [[SUPasswordPrompt alloc] initWithHost:host];
NSString *password = nil;
if([prompt run])
{
password = [prompt password];
}
[prompt release];
[invocation setArgument:&password atIndex:2];
[invocation invoke];
}
- (void)installAndRestart: (id)sender
{
[self installWithToolAndRelaunch:YES];
}
- (void)installWithToolAndRelaunch:(BOOL)relaunch
{
[statusController beginActionWithTitle:SULocalizedString(@"Installing update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
[statusController setButtonEnabled:NO];
[super installWithToolAndRelaunch:relaunch];
// if a user chooses to NOT relaunch the app (as is the case with WebKit
// when it asks you if you are sure you want to close the app with multiple
// tabs open), the status window still stays on the screen and obscures
// other windows; with this fix, it doesn't
if (statusController)
{
[statusController close];
[statusController autorelease];
statusController = nil;
}
}
- (void)abortUpdateWithError:(NSError *)error
{
NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"Update Error!", nil) defaultButton:SULocalizedString(@"Cancel Update", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:[error localizedDescription]];
[self showModalAlert:alert];
[super abortUpdateWithError:error];
}
- (void)abortUpdate
{
if (statusController)
{
[statusController close];
[statusController autorelease];
statusController = nil;
}
[super abortUpdate];
}
- (void)showModalAlert:(NSAlert *)alert
{
if ([[updater delegate] respondsToSelector:@selector(updaterWillShowModalAlert:)])
[[updater delegate] updaterWillShowModalAlert: updater];
// When showing a modal alert we need to ensure that background applications
// are focused to inform the user since there is no dock icon to notify them.
if ([host isBackgroundApplication]) { [NSApp activateIgnoringOtherApps:YES]; }
[alert setIcon:[host icon]];
[alert runModal];
if ([[updater delegate] respondsToSelector:@selector(updaterDidShowModalAlert:)])
[[updater delegate] updaterDidShowModalAlert: updater];
}
@end