| Index: chrome/installer/mac/app/Unpacker.m
|
| diff --git a/chrome/installer/mac/app/Unpacker.m b/chrome/installer/mac/app/Unpacker.m
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f3b9072870cc8256883c731a0ba5ee083df283e2
|
| --- /dev/null
|
| +++ b/chrome/installer/mac/app/Unpacker.m
|
| @@ -0,0 +1,167 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "Unpacker.h"
|
| +
|
| +#import <AppKit/AppKit.h>
|
| +#include <DiskArbitration/DiskArbitration.h>
|
| +#include <dispatch/dispatch.h>
|
| +
|
| +#import "Downloader.h"
|
| +
|
| +@interface Unpacker () {
|
| + NSURL* temporaryDirectoryURL_;
|
| + NSString* mountPath_;
|
| +
|
| + NSTask* __weak mountTask_;
|
| +
|
| + DASessionRef session_;
|
| + dispatch_queue_t unpack_dq_;
|
| +}
|
| +- (void)didFinishEjectingDisk:(DADiskRef)disk
|
| + withDissenter:(DADissenterRef)dissenter;
|
| +@end
|
| +
|
| +static void eject_callback(DADiskRef disk,
|
| + DADissenterRef dissenter,
|
| + void* context) {
|
| + Unpacker* unpacker = (__bridge_transfer Unpacker*)context;
|
| + [unpacker didFinishEjectingDisk:disk withDissenter:dissenter];
|
| +}
|
| +
|
| +static void unmount_callback(DADiskRef disk,
|
| + DADissenterRef dissenter,
|
| + void* context) {
|
| + if (dissenter) {
|
| + Unpacker* unpacker = (__bridge Unpacker*)context;
|
| + [unpacker didFinishEjectingDisk:disk withDissenter:dissenter];
|
| + } else {
|
| + DADiskEject(disk, kDADiskEjectOptionDefault, eject_callback, context);
|
| + }
|
| +}
|
| +
|
| +@implementation Unpacker
|
| +
|
| +@synthesize delegate = delegate_;
|
| +@synthesize appPath = appPath_;
|
| +
|
| +- (void)cleanUp {
|
| + [mountTask_ terminate];
|
| + // It's not the end of the world if this temporary directory is not removed
|
| + // here. It will be deleted when the operating system itself decides to
|
| + // anyway.
|
| + [[NSFileManager defaultManager] removeItemAtURL:temporaryDirectoryURL_
|
| + error:nil];
|
| + [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| +}
|
| +
|
| +// TODO: the failure delegate methods need to be revised to be more meaningfully
|
| +// deal with the errors (pipe in stderr / stdout)
|
| +- (void)mountDMGFromURL:(NSURL*)fileURL {
|
| + NSError* error = nil;
|
| + temporaryDirectoryURL_ = [[NSFileManager defaultManager]
|
| + URLForDirectory:NSItemReplacementDirectory
|
| + inDomain:NSUserDomainMask
|
| + appropriateForURL:[NSURL fileURLWithPath:@"/" isDirectory:YES]
|
| + create:YES
|
| + error:&error];
|
| + if (error) {
|
| + [delegate_ unpacker:self onMountFailure:error];
|
| + return;
|
| + }
|
| +
|
| + NSURL* temporaryDiskImageURL =
|
| + [temporaryDirectoryURL_ URLByAppendingPathComponent:@"GoogleChrome.dmg"];
|
| + mountPath_ = [[temporaryDirectoryURL_ URLByAppendingPathComponent:@"mnt"
|
| + isDirectory:YES] path];
|
| + [[NSFileManager defaultManager] createDirectoryAtPath:mountPath_
|
| + withIntermediateDirectories:YES
|
| + attributes:nil
|
| + error:&error];
|
| + if (error) {
|
| + [delegate_ unpacker:self onMountFailure:error];
|
| + return;
|
| + }
|
| +
|
| + // If the user closes the app at any time, we make sure that the cleanUp
|
| + // function deletes the temporary folder we just created.
|
| + [[NSNotificationCenter defaultCenter]
|
| + addObserver:self
|
| + selector:@selector(cleanUp)
|
| + name:NSApplicationWillTerminateNotification
|
| + object:nil];
|
| +
|
| + [[NSFileManager defaultManager] moveItemAtURL:fileURL
|
| + toURL:temporaryDiskImageURL
|
| + error:nil];
|
| +
|
| + NSString* path = @"/usr/bin/hdiutil";
|
| + NSArray* args = @[
|
| + @"attach", temporaryDiskImageURL, @"-nobrowse", @"-noverify",
|
| + @"-mountpoint", mountPath_
|
| + ];
|
| +
|
| + NSTask* mountTask = [[NSTask alloc] init];
|
| + mountTask.launchPath = path;
|
| + mountTask.arguments = args;
|
| + mountTask.terminationHandler = ^void(NSTask* task) {
|
| + NSError* error = nil;
|
| + NSString* diskAppPath =
|
| + [NSString pathWithComponents:@[ mountPath_, @"Google Chrome.app" ]];
|
| + NSString* tempAppPath = [[temporaryDirectoryURL_
|
| + URLByAppendingPathComponent:@"Google Chrome.app"] path];
|
| + [[NSFileManager defaultManager] copyItemAtPath:diskAppPath
|
| + toPath:tempAppPath
|
| + error:&error];
|
| + if (error) {
|
| + [delegate_ unpacker:self onMountFailure:error];
|
| + } else {
|
| + [delegate_ unpacker:self onMountSuccess:tempAppPath];
|
| + }
|
| + };
|
| + mountTask_ = mountTask;
|
| + [mountTask launch];
|
| +}
|
| +
|
| +- (void)unmountDMG {
|
| + session_ = DASessionCreate(nil);
|
| + unpack_dq_ =
|
| + dispatch_queue_create("com.google.chrome.unpack", DISPATCH_QUEUE_SERIAL);
|
| + DASessionSetDispatchQueue(session_, unpack_dq_);
|
| + DADiskRef child_disk = DADiskCreateFromVolumePath(
|
| + nil, session_,
|
| + (__bridge CFURLRef)[NSURL fileURLWithPath:mountPath_ isDirectory:YES]);
|
| + DADiskRef whole_disk = DADiskCopyWholeDisk(child_disk);
|
| +
|
| + DADiskUnmount(whole_disk,
|
| + kDADiskUnmountOptionWhole | kDADiskUnmountOptionForce,
|
| + unmount_callback, (__bridge_retained void*)self);
|
| +
|
| + CFRelease(whole_disk);
|
| + CFRelease(child_disk);
|
| +}
|
| +
|
| +- (void)didFinishEjectingDisk:(DADiskRef)disk
|
| + withDissenter:(DADissenterRef)dissenter {
|
| + DASessionSetDispatchQueue(session_, NULL);
|
| + dispatch_release(unpack_dq_);
|
| + CFRelease(session_);
|
| + NSError* error = nil;
|
| + if (dissenter) {
|
| + DAReturn status = DADissenterGetStatus(dissenter);
|
| + error = [NSError
|
| + errorWithDomain:@"ChromeErrorDomain"
|
| + code:err_get_code(status)
|
| + userInfo:@{
|
| + NSLocalizedDescriptionKey :
|
| + (__bridge NSString*)DADissenterGetStatusString(dissenter)
|
| + }];
|
| + [delegate_ unpacker:self onUnmountFailure:error];
|
| + } else {
|
| + [self cleanUp];
|
| + [delegate_ unpacker:self onUnmountSuccess:mountPath_];
|
| + }
|
| +}
|
| +
|
| +@end
|
|
|