Commit d0136826 authored by zengster's avatar zengster Committed by Commit bot

Added unpacking step

BUG=

Review-Url: https://codereview.chromium.org/2203583002
Cr-Commit-Position: refs/heads/master@{#415213}
parent 7056550d
......@@ -7,9 +7,6 @@
#import <AppKit/AppKit.h>
#import "Downloader.h"
#import "OmahaCommunication.h"
@interface AppDelegate : NSObject<NSApplicationDelegate>
@end
......
......@@ -4,10 +4,15 @@
#import "AppDelegate.h"
#include <Security/Security.h>
#import "Downloader.h"
#import "InstallerWindowController.h"
#import "NSError+ChromeInstallerAdditions.h"
#import "NSAlert+ChromeInstallerAdditions.h"
#import "AuthorizedInstall.h"
#import "OmahaCommunication.h"
#import "Unpacker.h"
@interface NSAlert ()
- (void)beginSheetModalForWindow:(NSWindow*)sheetWindow
......@@ -15,11 +20,16 @@
(void (^__nullable)(NSModalResponse returnCode))handler;
@end
@interface AppDelegate ()<OmahaCommunicationDelegate, DownloaderDelegate> {
@interface AppDelegate ()<NSWindowDelegate,
OmahaCommunicationDelegate,
DownloaderDelegate,
UnpackDelegate> {
InstallerWindowController* installerWindowController_;
AuthorizedInstall* authorizedInstall_;
BOOL preventTermination_;
}
@property(strong) NSWindow* window;
- (void)exit;
@end
@implementation AppDelegate
......@@ -28,6 +38,7 @@
// Sets up the main window and begins the downloading process.
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
// TODO: fix UI not loading until after asking for authorization.
window_.delegate = self;
installerWindowController_ =
[[InstallerWindowController alloc] initWithWindow:window_];
authorizedInstall_ = [[AuthorizedInstall alloc] init];
......@@ -41,26 +52,54 @@
- (void)applicationWillTerminate:(NSNotification*)aNotification {
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender {
- (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication*)sender {
return preventTermination_ ? NSTerminateCancel : NSTerminateNow;
}
// This function effectively takes the place of
// applicationShouldTerminateAfterLastWindowClosed: to make sure that the
// application does correctly terminate after closing the installer, but does
// not terminate when we call orderOut: to hide the installer during its
// tear-down steps.
- (BOOL)windowShouldClose:(id)sender {
[self exit];
return YES;
}
- (void)exit {
preventTermination_ = NO;
[NSApp terminate:nil];
}
- (void)startDownload {
[installerWindowController_ updateStatusDescription:@"Initializing..."];
OmahaCommunication* omahaMessenger = [[OmahaCommunication alloc] init];
omahaMessenger.delegate = self;
[omahaMessenger fetchDownloadURLs];
}
- (void)onOmahaSuccessWithURLs:(NSArray*)URLs {
- (void)onLoadInstallationToolFailure {
NSError* loadToolError = [NSError
errorForAlerts:@"Could not load installion tool"
withDescription:
@"Your Chrome Installer may be corrupted. Download and try again."
isRecoverable:NO];
[self displayError:loadToolError];
}
- (void)omahaCommunication:(OmahaCommunication*)messenger
onSuccess:(NSArray*)URLs {
[installerWindowController_ updateStatusDescription:@"Downloading..."];
Downloader* download = [[Downloader alloc] init];
download.delegate = self;
[download downloadChromeImageToDownloadsDirectory:[URLs firstObject]];
[download downloadChromeImageFrom:[URLs firstObject]];
}
- (void)onOmahaFailureWithError:(NSError*)error {
- (void)omahaCommunication:(OmahaCommunication*)messenger
onFailure:(NSError*)error {
NSError* networkError =
[NSError errorForAlerts:@"Network Error"
withDescription:@"Could not connect to Chrome server."
......@@ -70,24 +109,25 @@
// Bridge method from Downloader to InstallerWindowController. Allows Downloader
// to update the progressbar without having direct access to any UI obejcts.
- (void)didDownloadData:(double)downloadProgressPercentage {
[installerWindowController_
updateDownloadProgress:(double)downloadProgressPercentage];
- (void)downloader:(Downloader*)download percentProgress:(double)percentage {
[installerWindowController_ updateDownloadProgress:(double)percentage];
}
- (void)downloader:(Downloader*)download
onDownloadSuccess:(NSURL*)diskImagePath {
[installerWindowController_ updateStatusDescription:@"Done."];
- (void)downloader:(Downloader*)download onSuccess:(NSURL*)diskImageURL {
[installerWindowController_ updateStatusDescription:@"Installing..."];
[installerWindowController_ enableLaunchButton];
// TODO: Add unpacking step here and pass the path to the app bundle inside
// the mounted disk image path to startInstall. Currently passing hardcoded
// path to preunpacked app bundle.
//[authorizedInstall_
// startInstall:@"$HOME/Downloads/Google Chrome.app"];
Unpacker* unpacker = [[Unpacker alloc] init];
unpacker.delegate = self;
[unpacker mountDMGFromURL:diskImageURL];
}
- (void)downloader:(Downloader*)download
onDownloadFailureWithError:(NSError*)error {
- (void)downloader:(Downloader*)download onFailure:(NSError*)error {
NSError* downloadError =
[NSError errorForAlerts:@"Download Failure"
withDescription:@"Unable to download Google Chrome."
......@@ -95,24 +135,100 @@
[self displayError:downloadError];
}
- (void)onLoadInstallationToolFailure {
NSError* loadToolError = [NSError
errorForAlerts:@"Could not load installion tool"
withDescription:
@"Your Chrome Installer may be corrupted. Download and try again."
isRecoverable:NO];
[self displayError:loadToolError];
- (void)unpacker:(Unpacker*)unpacker onMountSuccess:(NSString*)tempAppPath {
SecStaticCodeRef diskStaticCode;
SecRequirementRef diskRequirement;
// TODO: flush out error handling more
OSStatus oserror;
oserror = SecStaticCodeCreateWithPath(
(__bridge CFURLRef)[NSURL fileURLWithPath:tempAppPath isDirectory:NO],
kSecCSDefaultFlags, &diskStaticCode);
if (oserror != errSecSuccess)
NSLog(@"code %d", oserror);
// TODO: add in a more specific code sign requirement
oserror =
SecRequirementCreateWithString((CFStringRef) @"anchor apple generic",
kSecCSDefaultFlags, &diskRequirement);
if (oserror != errSecSuccess)
NSLog(@"requirement %d", oserror);
oserror = SecStaticCodeCheckValidity(diskStaticCode, kSecCSDefaultFlags,
diskRequirement);
if (oserror != errSecSuccess)
NSLog(@"static code %d", oserror);
// Calling this function will change the progress bar into an indeterminate
// one. We won't need to update the progress bar any more after this point.
[installerWindowController_ updateDownloadProgress:-1.0];
// By disabling closing the window or quitting, we can tell the user that
// closing the application at this point is not a good idea.
window_.styleMask &= ~NSClosableWindowMask;
preventTermination_ = YES;
// TODO: move the below code into AuthorizedInstall
NSString* chromeInApplicationsFolder = @"/Applications/Google Chromo.app";
NSError* error = nil;
if ([[NSFileManager defaultManager]
fileExistsAtPath:chromeInApplicationsFolder]) {
[[NSFileManager defaultManager] moveItemAtPath:chromeInApplicationsFolder
toPath:tempAppPath
error:nil];
}
if (![[NSFileManager defaultManager] moveItemAtPath:tempAppPath
toPath:chromeInApplicationsFolder
error:&error]) {
NSLog(@"%@", error);
}
// TODO: move the above code into AuthorizedInstall
[[NSWorkspace sharedWorkspace]
launchApplicationAtURL:[NSURL fileURLWithPath:chromeInApplicationsFolder
isDirectory:NO]
options:NSWorkspaceLaunchDefault
configuration:@{}
error:&error];
if (error) {
NSLog(@"Chrome failed to launch: %@", error);
}
// Begin teardown stuff!
dispatch_async(dispatch_get_main_queue(), ^{
[window_ orderOut:nil];
});
[unpacker unmountDMG];
}
- (void)unpacker:(Unpacker*)unpacker onMountFailure:(NSError*)error {
NSError* extractError =
[NSError errorForAlerts:@"Install Failure"
withDescription:@"Unable to add Google Chrome to Applications."
isRecoverable:NO];
[self displayError:extractError];
}
- (void)unpacker:(Unpacker*)unpacker onUnmountSuccess:(NSString*)mountpath {
NSLog(@"we're done here!");
[self exit];
}
- (void)unpacker:(Unpacker*)unpacker onUnmountFailure:(NSError*)error {
NSLog(@"error unmounting");
// NOTE: since we are not deleting the temporary folder if the unmount fails,
// we'll just leave it up to the computer to delete the temporary folder on
// its own time, and to unmount the disk during a restart at some point. There
// is no other work to be done in the mean time.
[self exit];
}
// Displays an alert on the main window using the contents of the passed in
// error.
- (void)displayError:(NSError*)error {
NSAlert* alertForUser = [NSAlert alertWithError:error];
dispatch_async(dispatch_get_main_queue(), ^{
[alertForUser beginSheetModalForWindow:window_
completionHandler:^(NSModalResponse returnCode) {
if (returnCode != [alertForUser quitButton]) {
if (returnCode != [alertForUser quitResponse]) {
[self startDownload];
} else {
[NSApp terminate:nil];
......
......@@ -6,23 +6,37 @@ import("//testing/test.gni")
import("//build/config/mac/rules.gni")
source_set("mac_installer_base") {
visibility = [ ":*" ]
sources = [
"Downloader.h",
"Downloader.m",
"NSAlert+ChromeInstallerAdditions.h",
"NSAlert+ChromeInstallerAdditions.m",
"NSError+ChromeInstallerAdditions.h",
"NSError+ChromeInstallerAdditions.m",
"OmahaCommunication.h",
"OmahaCommunication.m",
"OmahaXMLParser.h",
"OmahaXMLParser.m",
"OmahaXMLRequest.h",
"OmahaXMLRequest.m",
"SystemInfo.h",
"SystemInfo.m",
"Unpacker.h",
"Unpacker.m",
]
public_configs = [ "//build/config/compiler:enable_arc" ]
}
mac_app_bundle("mac_installer_app") {
info_plist = "Info.plist"
extra_substitutions = [ "MACOSX_DEPLOYMENT_TARGET=10.9" ]
sources = [
"AppDelegate.h",
"AppDelegate.m",
"AuthorizedInstall.h",
"AuthorizedInstall.m",
"InstallerWindowController.h",
"InstallerWindowController.m",
"main.m",
]
......@@ -34,8 +48,8 @@ mac_app_bundle("mac_installer_app") {
]
libs = [
"AppKit.framework",
"CoreFoundation.framework",
"Cocoa.framework",
"DiskArbitration.framework",
"Security.framework",
]
}
......@@ -59,6 +73,7 @@ test("mac_installer_unittests") {
sources = [
"testing/OmahaXMLRequest_test.mm",
"testing/SystemInfo_test.mm",
"testing/Unpacker_test.mm",
]
deps = [
":mac_installer_base",
......@@ -66,10 +81,15 @@ test("mac_installer_unittests") {
"//base/test:run_all_unittests",
"//testing/gtest:gtest",
]
libs = [ "Foundation.framework" ]
libs = [
"Cocoa.framework",
"DiskArbitration.framework",
"Security.framework",
]
data = [
"testing/requestCheck.dtd",
"testing/requestSample.xml",
"testing/responseExample.xml",
"//chrome/test/data/mac_installer/requestCheck.dtd",
"//chrome/test/data/mac_installer/requestSample.xml",
"//chrome/test/data/mac_installer/responseExample.xml",
"//chrome/test/data/mac_installer/test-dmg.dmg",
]
}
......@@ -9,23 +9,17 @@
@class Downloader;
@protocol DownloaderDelegate
- (void)didDownloadData:(double)downloadProgressPercentage;
- (void)downloader:(Downloader*)download
onDownloadSuccess:(NSURL*)diskImagePath;
- (void)downloader:(Downloader*)download
onDownloadFailureWithError:(NSError*)error;
- (void)downloader:(Downloader*)download percentProgress:(double)percentage;
- (void)downloader:(Downloader*)download onSuccess:(NSURL*)diskImageURL;
- (void)downloader:(Downloader*)download onFailure:(NSError*)error;
@end
@interface Downloader : NSObject<NSURLSessionDownloadDelegate>
@property(nonatomic, assign) id<DownloaderDelegate> delegate;
// Takes an NSData with a response XML from Omaha and writes the latest
// version of chrome to the user's download directory.
- (void)downloadChromeImageToDownloadsDirectory:(NSURL*)chromeImageURL;
// Returns a path to a user's home download folder.
+ (NSString*)getChromeDownloadFilePath;
// Downloads Chrome from |chromeImageURL| to the local hard drive.
- (void)downloadChromeImageFrom:(NSURL*)chromeImageURL;
@end
......
......@@ -10,17 +10,9 @@
@synthesize delegate = delegate_;
+ (NSString*)getChromeDownloadFilePath {
NSArray* downloadPaths = NSSearchPathForDirectoriesInDomains(
NSDownloadsDirectory, NSUserDomainMask, YES);
NSString* completeFilePath = [NSString
pathWithComponents:@[ [downloadPaths firstObject], @"GoogleChrome.dmg" ]];
return completeFilePath;
}
// Downloads contents of chromeURL to downloads folders and delegates the work
// to the DownloadDelegate class.
- (void)downloadChromeImageToDownloadsDirectory:(NSURL*)chromeImageURL {
- (void)downloadChromeImageFrom:(NSURL*)chromeImageURL {
NSURLSession* session =
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration
defaultSessionConfiguration]
......@@ -38,7 +30,7 @@
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
double downloadProgressPercentage =
(double)totalBytesWritten / totalBytesExpectedToWrite * 100.0;
[delegate_ didDownloadData:downloadProgressPercentage];
[delegate_ downloader:self percentProgress:downloadProgressPercentage];
}
// Delegate method to move downloaded disk image to user's Download directory.
......@@ -46,24 +38,14 @@
downloadTask:(NSURLSessionDownloadTask*)downloadTask
didFinishDownloadingToURL:(NSURL*)location {
assert([location isFileURL]);
NSFileManager* manager = [NSFileManager defaultManager];
NSURL* downloadsDirectory =
[NSURL fileURLWithPath:[Downloader getChromeDownloadFilePath]];
NSError* fileManagerError = nil;
[manager moveItemAtURL:location
toURL:downloadsDirectory
error:&fileManagerError];
if (fileManagerError) {
[delegate_ downloader:self onDownloadFailureWithError:fileManagerError];
}
[delegate_ downloader:self onDownloadSuccess:location];
[delegate_ downloader:self onSuccess:location];
}
- (void)URLSession:(NSURLSession*)session
task:(NSURLSessionTask*)task
didCompleteWithError:(NSError*)error {
if (error) {
[delegate_ downloader:self onDownloadFailureWithError:error];
[delegate_ downloader:self onFailure:error];
}
}
......
......@@ -120,7 +120,13 @@
}
- (void)updateDownloadProgress:(double)progressPercent {
progressBar_.doubleValue = progressPercent;
if (progressPercent > 0.0) {
progressBar_.doubleValue = progressPercent;
} else {
progressBar_.doubleValue = 0.0;
progressBar_.indeterminate = YES;
[progressBar_ startAnimation:nil];
}
}
- (void)enableLaunchButton {
......
......@@ -9,7 +9,9 @@
typedef NSInteger NSModalResponse;
@interface NSAlert (ChromeInstallerAdditions)
- (NSModalResponse)quitButton;
// Allows the caller to determine whether to determine the app's quit button was
// pressed or not.
- (NSModalResponse)quitResponse;
@end
#endif // CHROME_INSTALLER_MAC_APP_NSALERT_CHROMEINSTALLERADDITIONS_H_
......@@ -8,7 +8,7 @@
// In the one-button scenario, the button would be just "Quit." In the
// two-button scenario, the first button would allow the user to "Retry" and
// the second button would provide the "Quit" option.
- (NSModalResponse)quitButton {
- (NSModalResponse)quitResponse {
return ([[self buttons] count] == 1) ? NSAlertFirstButtonReturn
: NSAlertSecondButtonReturn;
}
......
......@@ -8,6 +8,8 @@
#import <Foundation/Foundation.h>
@interface NSError (ChromeInstallerAdditions)
// Creates a custom error object to be used as the popup alert that the user
// will be shown.
+ (NSError*)errorForAlerts:(NSString*)message
withDescription:(NSString*)description
isRecoverable:(BOOL)recoverable;
......
......@@ -5,8 +5,6 @@
#import "NSError+ChromeInstallerAdditions.h"
@implementation NSError (ChromeInstallerAdditions)
// Creates a custom error object to be used as the popup alert that the user
// will be shown.
+ (NSError*)errorForAlerts:(NSString*)message
withDescription:(NSString*)description
isRecoverable:(BOOL)recoverable {
......
......@@ -7,9 +7,12 @@
#import <Foundation/Foundation.h>
@class OmahaCommunication;
@protocol OmahaCommunicationDelegate
- (void)onOmahaSuccessWithURLs:(NSArray*)URLs;
- (void)onOmahaFailureWithError:(NSError*)error;
- (void)omahaCommunication:(OmahaCommunication*)messenger
onSuccess:(NSArray*)URLs;
- (void)omahaCommunication:(OmahaCommunication*)messenger
onFailure:(NSError*)error;
@end
@interface OmahaCommunication : NSObject<NSURLSessionDataDelegate>
......
......@@ -7,6 +7,7 @@
#import "OmahaXMLRequest.h"
#import "OmahaXMLParser.h"
// TODO: turn this string to a command-line flag
static NSString* const omahaURLPath =
@"https://tools.google.com/service/update2";
......@@ -35,7 +36,7 @@ static NSString* const omahaURLPath =
}
- (void)fetchDownloadURLs {
// TODO: turn this string to a command-line flag
// Forming the request
NSURL* requestURL = [NSURL URLWithString:omahaURLPath];
NSMutableURLRequest* request =
[NSMutableURLRequest requestWithURL:requestURL];
......@@ -44,6 +45,7 @@ static NSString* const omahaURLPath =
[[requestXMLBody_ XMLString] dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = requestBody;
request.HTTPMethod = @"POST";
// Sending the request
[[[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData* data, NSURLResponse* response,
......@@ -56,9 +58,9 @@ static NSString* const omahaURLPath =
// parsing error, as the user only needs to know there was a problem
// talking with the Google Update server.
if (error) {
[delegate_ onOmahaFailureWithError:error];
[delegate_ omahaCommunication:self onFailure:error];
} else {
[delegate_ onOmahaSuccessWithURLs:completeURLs];
[delegate_ omahaCommunication:self onSuccess:completeURLs];
}
}] resume];
}
......
......@@ -9,8 +9,9 @@
@interface OmahaXMLParser : NSObject
// Parses an XML document and extracts all the URLs found as well as the
// filename. Adds each URL into the array chromeIncompleteDownloadURLs_.
// Parses an XML document and extracts the URLs and name of the Chrome DMG from
// Omaha, then returns an array with all the URLs concatenated with the
// filename.
+ (NSArray*)parseXML:(NSData*)omahaResponseXML error:(NSError**)error;
@end
......
......@@ -21,8 +21,6 @@
[parser setDelegate:omahaParser];
if (![parser parse]) {
*error = [parser parserError];
// TODO: pass up error object to indicate error occurred so
// InstallerWindowController can create custom user error message.
return nil;
}
......@@ -34,6 +32,9 @@
}
if ([completeDownloadURLs count] < 1) {
// TODO: currently whatever error is passed in doesn't matter... we should
// make it so that the type of error informs what the installer will do
// about the error
*error = [NSError errorWithDomain:@"ChromeErrorDomain" code:1 userInfo:nil];
return nil;
}
......
......@@ -23,9 +23,7 @@
// user attributes that Omaha actually looks at. The other parameters are useful
// for logging purposes but otherwise not directly used.
+ (NSXMLDocument*)createXMLRequestBody {
// TODO: might be a good idea in the future to add a version# for this
// installer using [[NSBundle mainBundle]
// objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]
// TODO: not hard-code protocol version #?
NSString* protocol = @"3.0";
NSString* platform = @"mac";
......
// 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.
#ifndef CHROME_INSTALLER_MAC_APP_UNPACKER_H_
#define CHROME_INSTALLER_MAC_APP_UNPACKER_H_
#import <Foundation/Foundation.h>
@class Unpacker;
@protocol UnpackDelegate<NSObject>
- (void)unpacker:(Unpacker*)unpacker onMountSuccess:(NSString*)tempAppPath;
- (void)unpacker:(Unpacker*)unpacker onMountFailure:(NSError*)error;
- (void)unpacker:(Unpacker*)unpacker onUnmountSuccess:(NSString*)mountpath;
- (void)unpacker:(Unpacker*)unpacker onUnmountFailure:(NSError*)error;
@end
@interface Unpacker : NSObject
@property(nonatomic, assign) id<UnpackDelegate> delegate;
@property(nonatomic, copy) NSString* appPath;
// Mount a disk image at |fileURL|.
- (void)mountDMGFromURL:(NSURL*)fileURL;
// Unmount that same disk image.
- (void)unmountDMG;
@end
#endif // CHROME_INSTALLER_MAC_APP_UNPACKER_H_
// 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
\ No newline at end of file
......@@ -4,21 +4,24 @@
#import "chrome/installer/mac/app/OmahaXMLRequest.h"
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/strings/sys_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
TEST(OmahaXMLRequestTest, CreateReturnsValidXML) {
base::scoped_nsobject<NSXMLDocument> xml_body_(
[OmahaXMLRequest createXMLRequestBody]);
NSXMLDocument* xml_body_ = [OmahaXMLRequest createXMLRequestBody];
ASSERT_TRUE(xml_body_);
NSString* requestDTDLocation = [[[[NSBundle mainBundle] bundlePath]
stringByAppendingPathComponent:
@"../../chrome/installer/mac/app/testing/requestCheck.dtd"]
stringByResolvingSymlinksInPath];
base::FilePath path;
PathService::Get(base::DIR_SOURCE_ROOT, &path);
path = path.AppendASCII("chrome/test/data/mac_installer/requestCheck.dtd");
NSString* requestDTDLocation = base::SysUTF8ToNSString(path.value());
NSData* requestDTDData = [NSData dataWithContentsOfFile:requestDTDLocation];
ASSERT_TRUE(requestDTDData);
......
// 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 "chrome/installer/mac/app/Unpacker.h"
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/strings/sys_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "chrome/installer/mac/app/Downloader.h"
@interface TestDelegate : NSObject<UnpackDelegate>
@property(nonatomic) BOOL pass;
@property(nonatomic) dispatch_semaphore_t test_semaphore;
- (void)fail;
- (void)succeed;
- (void)wait;
@end
@implementation TestDelegate
@synthesize pass = pass_;
@synthesize test_semaphore = test_semaphore_;
- (id)init {
if ((self = [super init])) {
test_semaphore_ = dispatch_semaphore_create(0);
pass_ = NO;
}
return self;
}
- (void)succeed {
pass_ = YES;
dispatch_semaphore_signal(test_semaphore_);
}
- (void)fail {
pass_ = NO;
dispatch_semaphore_signal(test_semaphore_);
}
- (void)wait {
dispatch_semaphore_wait(test_semaphore_, DISPATCH_TIME_FOREVER);
}
- (void)unpacker:(Unpacker*)unpacker onMountSuccess:(NSString*)tempAppPath {
if ([[NSFileManager defaultManager] fileExistsAtPath:tempAppPath]) {
[self succeed];
} else {
[self fail];
}
}
- (void)unpacker:(Unpacker*)unpacker onMountFailure:(NSError*)error {
[self fail];
}
- (void)unpacker:(Unpacker*)unpacker onUnmountSuccess:(NSString*)mountpath {
if (![[NSFileManager defaultManager]
fileExistsAtPath:[NSString pathWithComponents:@[
mountpath, @"Google Chrome.app"
]]]) {
[self succeed];
} else {
[self fail];
}
}
- (void)unpacker:(Unpacker*)unpacker onUnmountFailure:(NSError*)error {
[self fail];
}
@end
namespace {
TEST(UnpackerTest, IntegrationTest) {
// create objects and semaphore
Unpacker* unpack = [[Unpacker alloc] init];
TestDelegate* test_delegate = [[TestDelegate alloc] init];
unpack.delegate = test_delegate;
// get a disk image to use to test
base::FilePath originalPath;
PathService::Get(base::DIR_SOURCE_ROOT, &originalPath);
originalPath = originalPath.AppendASCII("chrome/test/data/mac_installer/");
base::FilePath copiedPath = base::FilePath(originalPath);
NSString* diskImageOriginalPath = base::SysUTF8ToNSString(
(originalPath.AppendASCII("test-dmg.dmg")).value());
NSString* diskImageCopiedPath = base::SysUTF8ToNSString(
(originalPath.AppendASCII("test-dmg2.dmg")).value());
[[NSFileManager defaultManager] copyItemAtPath:diskImageOriginalPath
toPath:diskImageCopiedPath
error:nil];
NSURL* dmgURL = [NSURL fileURLWithPath:diskImageCopiedPath isDirectory:NO];
// start mount step
[unpack mountDMGFromURL:dmgURL];
[test_delegate wait];
// is the disk image mounted?
ASSERT_TRUE([test_delegate pass]);
// start unmount step
[unpack unmountDMG];
[test_delegate wait];
// is the disk image gone?
EXPECT_TRUE([test_delegate pass]);
}
} // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment