2
// DownloadDataFilesPane.m
5
// Created by Jjgod Jiang on 4/21/09.
8
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
10
* Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
12
* The contents of this file are subject to the terms of either the GNU Lesser
13
* General Public License Version 2.1 only ("LGPL") or the Common Development and
14
* Distribution License ("CDDL")(collectively, the "License"). You may not use this
15
* file except in compliance with the License. You can obtain a copy of the CDDL at
16
* http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
17
* http://www.opensource.org/licenses/lgpl-license.php. See the License for the
18
* specific language governing permissions and limitations under the License. When
19
* distributing the software, include this License Header Notice in each file and
20
* include the full text of the License in the License file as well as the
23
* NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
25
* For Covered Software in this distribution, this License shall be governed by the
26
* laws of the State of California (excluding conflict-of-law provisions).
27
* Any litigation relating to this License shall be subject to the jurisdiction of
28
* the Federal Courts of the Northern District of California and the state courts
29
* of the State of California, with venue lying in Santa Clara County, California.
33
* If you wish your version of this file to be governed by only the CDDL or only
34
* the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
35
* include this software in this distribution under the [CDDL or LGPL Version 2.1]
36
* license." If you don't indicate a single choice of license, a recipient has the
37
* option to distribute your version of this file under either the CDDL or the LGPL
38
* Version 2.1, or to extend the choice of license to its licensees as provided
39
* above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
40
* Version 2 license, then the option applies only if the new code is made subject
41
* to such option by the copyright holder.
44
#import "DownloadDataFilesPane.h"
45
#import <CommonCrypto/CommonDigest.h>
46
#import <SecurityFoundation/SFAuthorization.h>
48
#define JJLocalizedString(key, comment) NSLocalizedStringFromTableInBundle(key, @"Localizable", [NSBundle bundleForClass: [self class]], comment)
51
#define kDataFileExt @".be"
53
#define kDataFileExt @".le"
56
NSDictionary *findInSpec(NSArray *specArray, NSString *file)
58
NSString *fileNameByArch = [file stringByAppendingString: kDataFileExt];
60
for (NSDictionary *dict in specArray)
61
if ([[dict objectForKey: @"Name"] isEqual: fileNameByArch])
67
@interface DownloadDataFilesPane (Private)
69
- (BOOL) doAuthorizedCopyFromPath: (NSString *) src toPath: (NSString *) dest withAuthorization: (SFAuthorization *) authorization;
70
- (SFAuthorization *) prepareAuthorization;
74
@implementation DownloadDataFilesPane
76
- (void) checkFile: (NSString *) file withSpec: (NSArray *) specArray
78
NSString *targetDirectory = [self objectForKeyInBundle: @"TargetInstallDirectory"];
79
NSString *path = [targetDirectory stringByAppendingPathComponent: file];
80
NSDictionary *fileDict = findInSpec(specArray, file);
84
NSLog(@"Cannot find %@ in %@", file, specArray);
88
if ([[NSFileManager defaultManager] fileExistsAtPath: path])
90
NSString *expectedHash = [fileDict objectForKey: @"SHA256"];
91
unsigned char hashedChars[CC_SHA256_DIGEST_LENGTH];
92
NSData *data = [NSData dataWithContentsOfFile: path];
94
CC_SHA256([data bytes], [data length], hashedChars);
99
NSMutableString *fileHash = [NSMutableString stringWithCapacity: CC_SHA256_DIGEST_LENGTH * 2];
101
for (i = 0; i < sizeof(hashedChars); i++)
102
[fileHash appendFormat: @"%02x", hashedChars[i]];
104
if (! [fileHash isEqualToString: expectedHash])
105
NSLog(@"file hash mismatch: %@ (now) vs. %@ (expected)", fileHash, expectedHash);
112
[filesToDownload addObject: fileDict];
113
totalBytesToDownload += [(NSNumber *)[fileDict objectForKey: @"Size"] unsignedIntValue];
118
return [[NSBundle bundleForClass: [self class]] localizedStringForKey: @"PaneTitle"
123
- (id) objectForKeyInBundle: (NSString *) key
125
return [[[NSBundle bundleForClass: [self class]] infoDictionary] objectForKey: key];
128
- (void) finishDownload
130
[mainTextField setStringValue: JJLocalizedString(@"All files are successfully downloaded.",
131
"All files downloaded.")];
132
[auxTextField setHidden: YES];
133
[getButton setHidden: YES];
134
[progress setHidden: YES];
136
[self setNextEnabled: YES];
137
[self setPreviousEnabled: YES];
144
SFAuthorization *authorization = [self prepareAuthorization];
148
for (NSString *file in filesDownloaded)
150
NSString *from = [@"/tmp" stringByAppendingPathComponent: file];
151
NSString *to = [[self objectForKeyInBundle: @"TargetInstallDirectory"] stringByAppendingPathComponent: file];
152
[self doAuthorizedCopyFromPath: from
154
withAuthorization: authorization];
158
[authorization invalidateCredentials];
163
- (void) downloadNextFile
168
// If all files are downloaded
169
if (! [filesToDownload count])
171
[self finishDownload];
174
dict = [filesToDownload objectAtIndex: 0];
176
NSURL *URL = [NSURL URLWithString: [dict objectForKey: @"URL"]];
178
NSLog(@"Start downloading: %@", URL);
180
// create the request
181
NSURLRequest *theRequest = [NSURLRequest requestWithURL: URL
182
cachePolicy: NSURLRequestUseProtocolCachePolicy
183
timeoutInterval: 30.0];
184
// create the connection with the request
185
// and start loading the data
186
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest: theRequest
190
currentDownload = theDownload;
191
NSString *destination = [@"/tmp" stringByAppendingPathComponent:
192
[[dict objectForKey: @"Name"] stringByDeletingPathExtension]];
193
NSLog(@"Set destination to %@", destination);
194
// set the destination file now
195
[theDownload setDestination: destination
196
allowOverwrite: YES];
198
// inform the user that the download could not be made
202
- (NSString *) _humanReadableSizeFromDouble: (double) value
205
return [NSString stringWithFormat:@"%.0lf %@", value,
206
JJLocalizedString(@"B", @"the unit for bytes")];
208
if (value < 1024 * 1024)
209
return [NSString stringWithFormat:@"%.0lf %@", value / 1024.0,
210
JJLocalizedString(@"KB", @"the unit for kilobytes")];
212
if (value < 1024 * 1024 * 1024)
213
return [NSString stringWithFormat:@"%.1lf %@", value / 1024.0 / 1024.0,
214
JJLocalizedString(@"MB", @"the unit for megabytes")];
216
return [NSString stringWithFormat:@"%.2lf %@", value / 1024.0 / 1024.0 / 1024.0,
217
JJLocalizedString(@"GB", @"the unit for gigabytes")];
220
- (IBAction) stopDownload: (id) sender
222
[currentDownload cancel];
223
[currentDownload release];
224
currentDownload = nil;
226
[getButton setTitle: JJLocalizedString(@"Start...", "Start downloading")];
227
[getButton setAction: @selector(startDownload:)];
228
[progress setDoubleValue: 0];
229
[progress setHidden: YES];
231
[self setNextEnabled: YES];
232
[self setPreviousEnabled: YES];
233
[auxTextField setStringValue: JJLocalizedString(@"Download stopped.", "Download stopped")];
236
- (IBAction) startDownload: (id) sender
238
[progress setHidden: NO];
239
[progress setDoubleValue: 0];
241
[getButton setTitle: JJLocalizedString(@"Cancel", "Stop downloading")];
242
[getButton setAction: @selector(stopDownload:)];
243
[auxTextField setStringValue: [NSString stringWithFormat: JJLocalizedString(@"%@ of %@", nil),
244
[self _humanReadableSizeFromDouble: 0],
245
[self _humanReadableSizeFromDouble: [progress maxValue]]]];
247
[self setNextEnabled: NO];
248
[self setPreviousEnabled: NO];
250
[self downloadNextFile];
253
- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
255
[filesToDownload removeObjectAtIndex: 0];
256
// release the connection
259
[self downloadNextFile];
261
NSLog(@"Download failed! Error - %@ %@",
262
[error localizedDescription],
263
[[error userInfo] objectForKey: NSURLErrorFailingURLStringErrorKey]);
266
- (void) download: (NSURLDownload *) download didReceiveDataOfLength: (NSUInteger) length
268
double value = [progress doubleValue] + length;
269
if (value > [progress maxValue])
270
value = [progress maxValue];
272
[progress setDoubleValue: value];
273
[auxTextField setStringValue: [NSString stringWithFormat: JJLocalizedString(@"%@ of %@", nil),
274
[self _humanReadableSizeFromDouble: value],
275
[self _humanReadableSizeFromDouble: [progress maxValue]]]];
278
- (void) downloadDidFinish: (NSURLDownload *) download
280
// release the connection
283
NSString *filename = [[[[[download request] URL] path] lastPathComponent] stringByDeletingPathExtension];
284
// do something with the data
285
NSLog(@"Finish downloading: %@", filename);
287
if ([filesToDownload count])
288
[filesToDownload removeObjectAtIndex: 0];
290
[filesDownloaded addObject: filename];
291
[self downloadNextFile];
294
- (void) startFetchingPropertyListFromURL: (NSURL *) URL
296
[mainTextField setStringValue: JJLocalizedString(@"Checking updates for data files...", "Checking property list on network")];
298
NSURLRequest *request = [NSURLRequest requestWithURL: URL];
299
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest: request
303
receivedData = [[NSMutableData data] retain];
306
// Failed to download plist
307
NSLog(@"Failed to open %@", [URL path]);
312
- (void) connection: (NSURLConnection *) connection didReceiveResponse: (NSURLResponse *) response
314
[receivedData setLength: 0];
317
- (void) connection: (NSURLConnection *) connection didReceiveData: (NSData *) data
319
[receivedData appendData: data];
322
- (void) connection: (NSURLConnection *) connection didFailWithError: (NSError *) error
324
// release the connection, and the data object
325
[connection release];
326
// receivedData is declared as a method instance elsewhere
327
[receivedData release];
330
NSLog(@"Connection failed! Error - %@ %@",
331
[error localizedDescription],
332
[[error userInfo] objectForKey: NSURLErrorFailingURLStringErrorKey]);
337
- (void) setSummaryString: (NSString *) summary detailString: (NSString *) detail
339
NSDictionary *summaryAttributes, *detailAttributes;
340
summaryAttributes = [NSDictionary dictionaryWithObject: [NSFont fontWithName: @"Lucida Grande" size: 13.0]
341
forKey: NSFontAttributeName];
342
detailAttributes = [NSDictionary dictionaryWithObject: [NSFont fontWithName: @"Monaco" size: 11.0]
343
forKey: NSFontAttributeName];
345
NSMutableAttributedString *stringToSet = [[NSMutableAttributedString alloc] initWithString: summary
346
attributes: summaryAttributes];
347
NSAttributedString *detailString = [[NSAttributedString alloc] initWithString: detail
348
attributes: detailAttributes];
349
[stringToSet appendAttributedString: [[[NSAttributedString alloc] initWithString: @"\n\n"] autorelease]];
350
[stringToSet appendAttributedString: detailString];
351
[detailString release];
352
[mainTextField setAttributedStringValue: stringToSet];
353
[stringToSet release];
356
- (void) connectionDidFinishLoading: (NSURLConnection *) connection
358
[connection release];
360
NSString *errorDesc = nil;
361
NSArray *specArray = (NSArray *)[NSPropertyListSerialization propertyListFromData: receivedData
362
mutabilityOption: NSPropertyListImmutable
364
errorDescription: &errorDesc];
365
[receivedData release];
369
NSLog(@"Failed to read property list: %@", errorDesc);
371
NSString *errorTitle = [NSString stringWithFormat: JJLocalizedString(@"Failed to fetch a valid data file list from %@:", nil),
372
[self objectForKeyInBundle: @"FilesSpecPlistURL"]];
374
[self setSummaryString: errorTitle
375
detailString: errorDesc];
381
NSArray *files = [NSArray arrayWithObjects: @"lm_sc.t3g", @"pydict_sc.bin", nil];
383
if (! filesToDownload)
384
filesToDownload = [[NSMutableArray alloc] initWithCapacity: 2];
386
if (! filesDownloaded)
387
filesDownloaded = [[NSMutableArray alloc] initWithCapacity: 2];
389
totalBytesToDownload = 0;
392
for (NSString *file in files)
393
[self checkFile: file withSpec: specArray];
395
if ([filesToDownload count])
397
NSString *text = [NSString stringWithFormat: JJLocalizedString(@"The following files need to be downloaded and save to\n%@:",
399
[self objectForKeyInBundle: @"TargetInstallDirectory"]];
400
NSMutableString *urls = [NSMutableString stringWithCapacity: 50];
401
for (NSDictionary *dict in filesToDownload)
402
[urls appendFormat: @"%@\n", [dict objectForKey: @"URL"]];
404
[self setSummaryString: text
407
[auxTextField setStringValue: JJLocalizedString(@"Click the Start button to download them automatically.",
408
"Click Download Button")];
409
[getButton setTitle: JJLocalizedString(@"Start...", "Start downloading")];
410
[getButton setHidden: NO];
411
[progress setStyle: NSProgressIndicatorBarStyle];
412
[progress setMaxValue: totalBytesToDownload];
413
[progress setIndeterminate: NO];
416
[mainTextField setStringValue: JJLocalizedString(@"You already have the latest data files, just Continue.", "Bypass")];
419
/* pane's entry point: code called when user enters this pane */
420
- (void) didEnterPane: (InstallerSectionDirection) dir
422
NSURL *plistURL = [NSURL URLWithString: [self objectForKeyInBundle: @"FilesSpecPlistURL"]];
423
[self startFetchingPropertyListFromURL: plistURL];
426
/* called when user clicks "Continue" -- return value indicates if application should exit pane */
427
- (BOOL) shouldExitPane: (InstallerSectionDirection) dir
432
[filesToDownload release];
433
filesToDownload = nil;
438
- (SFAuthorization *) prepareAuthorization
441
AuthorizationFlags authFlags = kAuthorizationFlagPreAuthorize |
442
kAuthorizationFlagExtendRights |
443
kAuthorizationFlagInteractionAllowed;
444
AuthorizationItem authItem = { kAuthorizationRightExecute, 0, nil, 0 };
445
AuthorizationRights authRights = { 1, &authItem };
446
SFAuthorization *authorization = [SFAuthorization authorizationWithFlags: authFlags
448
environment: kAuthorizationEmptyEnvironment];
449
return authorization;
452
// doAuthorizedCopyFromPath does an authorized copy, getting admin rights
454
// NOTE: when running the task with admin privileges, this waits on any child
455
// process, since AEWP doesn't tell us the child's pid. This could be fooled
456
// by any other child process that quits in the window between launch and
457
// completion of our actual tool.
458
- (BOOL) doAuthorizedCopyFromPath: (NSString *) src
459
toPath: (NSString *) dest
460
withAuthorization: (SFAuthorization *) authorization
463
const char taskPath[] = "/usr/bin/ditto";
464
const char* arguments[] = {
465
"-rsrcFork", // 0: copy resource forks; --rsrc requires 10.3
467
NULL, // 2: dest path
470
arguments[1] = [src fileSystemRepresentation];
471
arguments[2] = [dest fileSystemRepresentation];
473
FILE **kNoPipe = nil;
474
OSStatus status = AuthorizationExecuteWithPrivileges([authorization authorizationRef],
476
kAuthorizationFlagDefaults,
477
(char *const *)arguments,
479
if (status == errAuthorizationSuccess) {
481
int pid = wait(&wait_status);
482
if (pid == -1 || !WIFEXITED(wait_status)) {
485
status = WEXITSTATUS(wait_status);
489
return (status == 0);