OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "Unpacker.h" |
| 6 |
| 7 #import <AppKit/AppKit.h> |
| 8 #include <DiskArbitration/DiskArbitration.h> |
| 9 #include <dispatch/dispatch.h> |
| 10 |
| 11 #import "Downloader.h" |
| 12 |
| 13 @interface Unpacker () { |
| 14 NSURL* temporaryDirectoryURL_; |
| 15 NSString* mountPath_; |
| 16 |
| 17 NSTask* __weak mountTask_; |
| 18 |
| 19 DASessionRef session_; |
| 20 dispatch_queue_t unpack_dq_; |
| 21 } |
| 22 - (void)didFinishEjectingDisk:(DADiskRef)disk |
| 23 withDissenter:(DADissenterRef)dissenter; |
| 24 @end |
| 25 |
| 26 static void eject_callback(DADiskRef disk, |
| 27 DADissenterRef dissenter, |
| 28 void* context) { |
| 29 Unpacker* unpacker = (__bridge_transfer Unpacker*)context; |
| 30 [unpacker didFinishEjectingDisk:disk withDissenter:dissenter]; |
| 31 } |
| 32 |
| 33 static void unmount_callback(DADiskRef disk, |
| 34 DADissenterRef dissenter, |
| 35 void* context) { |
| 36 if (dissenter) { |
| 37 Unpacker* unpacker = (__bridge Unpacker*)context; |
| 38 [unpacker didFinishEjectingDisk:disk withDissenter:dissenter]; |
| 39 } else { |
| 40 DADiskEject(disk, kDADiskEjectOptionDefault, eject_callback, context); |
| 41 } |
| 42 } |
| 43 |
| 44 @implementation Unpacker |
| 45 |
| 46 @synthesize delegate = delegate_; |
| 47 @synthesize appPath = appPath_; |
| 48 |
| 49 - (void)cleanUp { |
| 50 [mountTask_ terminate]; |
| 51 // It's not the end of the world if this temporary directory is not removed |
| 52 // here. It will be deleted when the operating system itself decides to |
| 53 // anyway. |
| 54 [[NSFileManager defaultManager] removeItemAtURL:temporaryDirectoryURL_ |
| 55 error:nil]; |
| 56 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 57 } |
| 58 |
| 59 // TODO: the failure delegate methods need to be revised to be more meaningfully |
| 60 // deal with the errors (pipe in stderr / stdout) |
| 61 - (void)mountDMGFromURL:(NSURL*)fileURL { |
| 62 NSError* error = nil; |
| 63 temporaryDirectoryURL_ = [[NSFileManager defaultManager] |
| 64 URLForDirectory:NSItemReplacementDirectory |
| 65 inDomain:NSUserDomainMask |
| 66 appropriateForURL:[NSURL fileURLWithPath:@"/" isDirectory:YES] |
| 67 create:YES |
| 68 error:&error]; |
| 69 if (error) { |
| 70 [delegate_ unpacker:self onMountFailure:error]; |
| 71 return; |
| 72 } |
| 73 |
| 74 NSURL* temporaryDiskImageURL = |
| 75 [temporaryDirectoryURL_ URLByAppendingPathComponent:@"GoogleChrome.dmg"]; |
| 76 mountPath_ = [[temporaryDirectoryURL_ URLByAppendingPathComponent:@"mnt" |
| 77 isDirectory:YES] path]; |
| 78 [[NSFileManager defaultManager] createDirectoryAtPath:mountPath_ |
| 79 withIntermediateDirectories:YES |
| 80 attributes:nil |
| 81 error:&error]; |
| 82 if (error) { |
| 83 [delegate_ unpacker:self onMountFailure:error]; |
| 84 return; |
| 85 } |
| 86 |
| 87 // If the user closes the app at any time, we make sure that the cleanUp |
| 88 // function deletes the temporary folder we just created. |
| 89 [[NSNotificationCenter defaultCenter] |
| 90 addObserver:self |
| 91 selector:@selector(cleanUp) |
| 92 name:NSApplicationWillTerminateNotification |
| 93 object:nil]; |
| 94 |
| 95 [[NSFileManager defaultManager] moveItemAtURL:fileURL |
| 96 toURL:temporaryDiskImageURL |
| 97 error:nil]; |
| 98 |
| 99 NSString* path = @"/usr/bin/hdiutil"; |
| 100 NSArray* args = @[ |
| 101 @"attach", temporaryDiskImageURL, @"-nobrowse", @"-noverify", |
| 102 @"-mountpoint", mountPath_ |
| 103 ]; |
| 104 |
| 105 NSTask* mountTask = [[NSTask alloc] init]; |
| 106 mountTask.launchPath = path; |
| 107 mountTask.arguments = args; |
| 108 mountTask.terminationHandler = ^void(NSTask* task) { |
| 109 NSError* error = nil; |
| 110 NSString* diskAppPath = |
| 111 [NSString pathWithComponents:@[ mountPath_, @"Google Chrome.app" ]]; |
| 112 NSString* tempAppPath = [[temporaryDirectoryURL_ |
| 113 URLByAppendingPathComponent:@"Google Chrome.app"] path]; |
| 114 [[NSFileManager defaultManager] copyItemAtPath:diskAppPath |
| 115 toPath:tempAppPath |
| 116 error:&error]; |
| 117 if (error) { |
| 118 [delegate_ unpacker:self onMountFailure:error]; |
| 119 } else { |
| 120 [delegate_ unpacker:self onMountSuccess:tempAppPath]; |
| 121 } |
| 122 }; |
| 123 mountTask_ = mountTask; |
| 124 [mountTask launch]; |
| 125 } |
| 126 |
| 127 - (void)unmountDMG { |
| 128 session_ = DASessionCreate(nil); |
| 129 unpack_dq_ = |
| 130 dispatch_queue_create("com.google.chrome.unpack", DISPATCH_QUEUE_SERIAL); |
| 131 DASessionSetDispatchQueue(session_, unpack_dq_); |
| 132 DADiskRef child_disk = DADiskCreateFromVolumePath( |
| 133 nil, session_, |
| 134 (__bridge CFURLRef)[NSURL fileURLWithPath:mountPath_ isDirectory:YES]); |
| 135 DADiskRef whole_disk = DADiskCopyWholeDisk(child_disk); |
| 136 |
| 137 DADiskUnmount(whole_disk, |
| 138 kDADiskUnmountOptionWhole | kDADiskUnmountOptionForce, |
| 139 unmount_callback, (__bridge_retained void*)self); |
| 140 |
| 141 CFRelease(whole_disk); |
| 142 CFRelease(child_disk); |
| 143 } |
| 144 |
| 145 - (void)didFinishEjectingDisk:(DADiskRef)disk |
| 146 withDissenter:(DADissenterRef)dissenter { |
| 147 DASessionSetDispatchQueue(session_, NULL); |
| 148 dispatch_release(unpack_dq_); |
| 149 CFRelease(session_); |
| 150 NSError* error = nil; |
| 151 if (dissenter) { |
| 152 DAReturn status = DADissenterGetStatus(dissenter); |
| 153 error = [NSError |
| 154 errorWithDomain:@"ChromeErrorDomain" |
| 155 code:err_get_code(status) |
| 156 userInfo:@{ |
| 157 NSLocalizedDescriptionKey : |
| 158 (__bridge NSString*)DADissenterGetStatusString(dissenter) |
| 159 }]; |
| 160 [delegate_ unpacker:self onUnmountFailure:error]; |
| 161 } else { |
| 162 [self cleanUp]; |
| 163 [delegate_ unpacker:self onUnmountSuccess:mountPath_]; |
| 164 } |
| 165 } |
| 166 |
| 167 @end |
OLD | NEW |