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 @@ ...@@ -7,9 +7,6 @@
#import <AppKit/AppKit.h> #import <AppKit/AppKit.h>
#import "Downloader.h"
#import "OmahaCommunication.h"
@interface AppDelegate : NSObject<NSApplicationDelegate> @interface AppDelegate : NSObject<NSApplicationDelegate>
@end @end
......
...@@ -4,10 +4,15 @@ ...@@ -4,10 +4,15 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#include <Security/Security.h>
#import "Downloader.h"
#import "InstallerWindowController.h" #import "InstallerWindowController.h"
#import "NSError+ChromeInstallerAdditions.h" #import "NSError+ChromeInstallerAdditions.h"
#import "NSAlert+ChromeInstallerAdditions.h" #import "NSAlert+ChromeInstallerAdditions.h"
#import "AuthorizedInstall.h" #import "AuthorizedInstall.h"
#import "OmahaCommunication.h"
#import "Unpacker.h"
@interface NSAlert () @interface NSAlert ()
- (void)beginSheetModalForWindow:(NSWindow*)sheetWindow - (void)beginSheetModalForWindow:(NSWindow*)sheetWindow
...@@ -15,11 +20,16 @@ ...@@ -15,11 +20,16 @@
(void (^__nullable)(NSModalResponse returnCode))handler; (void (^__nullable)(NSModalResponse returnCode))handler;
@end @end
@interface AppDelegate ()<OmahaCommunicationDelegate, DownloaderDelegate> { @interface AppDelegate ()<NSWindowDelegate,
OmahaCommunicationDelegate,
DownloaderDelegate,
UnpackDelegate> {
InstallerWindowController* installerWindowController_; InstallerWindowController* installerWindowController_;
AuthorizedInstall* authorizedInstall_; AuthorizedInstall* authorizedInstall_;
BOOL preventTermination_;
} }
@property(strong) NSWindow* window; @property(strong) NSWindow* window;
- (void)exit;
@end @end
@implementation AppDelegate @implementation AppDelegate
...@@ -28,6 +38,7 @@ ...@@ -28,6 +38,7 @@
// Sets up the main window and begins the downloading process. // Sets up the main window and begins the downloading process.
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { - (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
// TODO: fix UI not loading until after asking for authorization. // TODO: fix UI not loading until after asking for authorization.
window_.delegate = self;
installerWindowController_ = installerWindowController_ =
[[InstallerWindowController alloc] initWithWindow:window_]; [[InstallerWindowController alloc] initWithWindow:window_];
authorizedInstall_ = [[AuthorizedInstall alloc] init]; authorizedInstall_ = [[AuthorizedInstall alloc] init];
...@@ -41,26 +52,54 @@ ...@@ -41,26 +52,54 @@
- (void)applicationWillTerminate:(NSNotification*)aNotification { - (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; return YES;
} }
- (void)exit {
preventTermination_ = NO;
[NSApp terminate:nil];
}
- (void)startDownload { - (void)startDownload {
[installerWindowController_ updateStatusDescription:@"Initializing..."]; [installerWindowController_ updateStatusDescription:@"Initializing..."];
OmahaCommunication* omahaMessenger = [[OmahaCommunication alloc] init]; OmahaCommunication* omahaMessenger = [[OmahaCommunication alloc] init];
omahaMessenger.delegate = self; omahaMessenger.delegate = self;
[omahaMessenger fetchDownloadURLs]; [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..."]; [installerWindowController_ updateStatusDescription:@"Downloading..."];
Downloader* download = [[Downloader alloc] init]; Downloader* download = [[Downloader alloc] init];
download.delegate = self; 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* networkError =
[NSError errorForAlerts:@"Network Error" [NSError errorForAlerts:@"Network Error"
withDescription:@"Could not connect to Chrome server." withDescription:@"Could not connect to Chrome server."
...@@ -70,24 +109,25 @@ ...@@ -70,24 +109,25 @@
// Bridge method from Downloader to InstallerWindowController. Allows Downloader // Bridge method from Downloader to InstallerWindowController. Allows Downloader
// to update the progressbar without having direct access to any UI obejcts. // to update the progressbar without having direct access to any UI obejcts.
- (void)didDownloadData:(double)downloadProgressPercentage { - (void)downloader:(Downloader*)download percentProgress:(double)percentage {
[installerWindowController_ [installerWindowController_ updateDownloadProgress:(double)percentage];
updateDownloadProgress:(double)downloadProgressPercentage];
} }
- (void)downloader:(Downloader*)download - (void)downloader:(Downloader*)download onSuccess:(NSURL*)diskImageURL {
onDownloadSuccess:(NSURL*)diskImagePath { [installerWindowController_ updateStatusDescription:@"Installing..."];
[installerWindowController_ updateStatusDescription:@"Done."];
[installerWindowController_ enableLaunchButton]; [installerWindowController_ enableLaunchButton];
// TODO: Add unpacking step here and pass the path to the app bundle inside // TODO: Add unpacking step here and pass the path to the app bundle inside
// the mounted disk image path to startInstall. Currently passing hardcoded // the mounted disk image path to startInstall. Currently passing hardcoded
// path to preunpacked app bundle. // path to preunpacked app bundle.
//[authorizedInstall_ //[authorizedInstall_
// startInstall:@"$HOME/Downloads/Google Chrome.app"]; // startInstall:@"$HOME/Downloads/Google Chrome.app"];
Unpacker* unpacker = [[Unpacker alloc] init];
unpacker.delegate = self;
[unpacker mountDMGFromURL:diskImageURL];
} }
- (void)downloader:(Downloader*)download - (void)downloader:(Downloader*)download onFailure:(NSError*)error {
onDownloadFailureWithError:(NSError*)error {
NSError* downloadError = NSError* downloadError =
[NSError errorForAlerts:@"Download Failure" [NSError errorForAlerts:@"Download Failure"
withDescription:@"Unable to download Google Chrome." withDescription:@"Unable to download Google Chrome."
...@@ -95,24 +135,100 @@ ...@@ -95,24 +135,100 @@
[self displayError:downloadError]; [self displayError:downloadError];
} }
- (void)onLoadInstallationToolFailure { - (void)unpacker:(Unpacker*)unpacker onMountSuccess:(NSString*)tempAppPath {
NSError* loadToolError = [NSError SecStaticCodeRef diskStaticCode;
errorForAlerts:@"Could not load installion tool" SecRequirementRef diskRequirement;
withDescription: // TODO: flush out error handling more
@"Your Chrome Installer may be corrupted. Download and try again." OSStatus oserror;
isRecoverable:NO]; oserror = SecStaticCodeCreateWithPath(
[self displayError:loadToolError]; (__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 // Displays an alert on the main window using the contents of the passed in
// error. // error.
- (void)displayError:(NSError*)error { - (void)displayError:(NSError*)error {
NSAlert* alertForUser = [NSAlert alertWithError:error]; NSAlert* alertForUser = [NSAlert alertWithError:error];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[alertForUser beginSheetModalForWindow:window_ [alertForUser beginSheetModalForWindow:window_
completionHandler:^(NSModalResponse returnCode) { completionHandler:^(NSModalResponse returnCode) {
if (returnCode != [alertForUser quitButton]) { if (returnCode != [alertForUser quitResponse]) {
[self startDownload]; [self startDownload];
} else { } else {
[NSApp terminate:nil]; [NSApp terminate:nil];
......
...@@ -6,23 +6,37 @@ import("//testing/test.gni") ...@@ -6,23 +6,37 @@ import("//testing/test.gni")
import("//build/config/mac/rules.gni") import("//build/config/mac/rules.gni")
source_set("mac_installer_base") { source_set("mac_installer_base") {
visibility = [ ":*" ]
sources = [ sources = [
"Downloader.h",
"Downloader.m", "Downloader.m",
"NSAlert+ChromeInstallerAdditions.h",
"NSAlert+ChromeInstallerAdditions.m", "NSAlert+ChromeInstallerAdditions.m",
"NSError+ChromeInstallerAdditions.h",
"NSError+ChromeInstallerAdditions.m", "NSError+ChromeInstallerAdditions.m",
"OmahaCommunication.h",
"OmahaCommunication.m", "OmahaCommunication.m",
"OmahaXMLParser.h",
"OmahaXMLParser.m", "OmahaXMLParser.m",
"OmahaXMLRequest.h",
"OmahaXMLRequest.m", "OmahaXMLRequest.m",
"SystemInfo.h",
"SystemInfo.m", "SystemInfo.m",
"Unpacker.h",
"Unpacker.m",
] ]
public_configs = [ "//build/config/compiler:enable_arc" ]
} }
mac_app_bundle("mac_installer_app") { mac_app_bundle("mac_installer_app") {
info_plist = "Info.plist" info_plist = "Info.plist"
extra_substitutions = [ "MACOSX_DEPLOYMENT_TARGET=10.9" ] extra_substitutions = [ "MACOSX_DEPLOYMENT_TARGET=10.9" ]
sources = [ sources = [
"AppDelegate.h",
"AppDelegate.m", "AppDelegate.m",
"AuthorizedInstall.h",
"AuthorizedInstall.m", "AuthorizedInstall.m",
"InstallerWindowController.h",
"InstallerWindowController.m", "InstallerWindowController.m",
"main.m", "main.m",
] ]
...@@ -34,8 +48,8 @@ mac_app_bundle("mac_installer_app") { ...@@ -34,8 +48,8 @@ mac_app_bundle("mac_installer_app") {
] ]
libs = [ libs = [
"AppKit.framework", "Cocoa.framework",
"CoreFoundation.framework", "DiskArbitration.framework",
"Security.framework", "Security.framework",
] ]
} }
...@@ -59,6 +73,7 @@ test("mac_installer_unittests") { ...@@ -59,6 +73,7 @@ test("mac_installer_unittests") {
sources = [ sources = [
"testing/OmahaXMLRequest_test.mm", "testing/OmahaXMLRequest_test.mm",
"testing/SystemInfo_test.mm", "testing/SystemInfo_test.mm",
"testing/Unpacker_test.mm",
] ]
deps = [ deps = [
":mac_installer_base", ":mac_installer_base",
...@@ -66,10 +81,15 @@ test("mac_installer_unittests") { ...@@ -66,10 +81,15 @@ test("mac_installer_unittests") {
"//base/test:run_all_unittests", "//base/test:run_all_unittests",
"//testing/gtest:gtest", "//testing/gtest:gtest",
] ]
libs = [ "Foundation.framework" ] libs = [
"Cocoa.framework",
"DiskArbitration.framework",
"Security.framework",
]
data = [ data = [
"testing/requestCheck.dtd", "//chrome/test/data/mac_installer/requestCheck.dtd",
"testing/requestSample.xml", "//chrome/test/data/mac_installer/requestSample.xml",
"testing/responseExample.xml", "//chrome/test/data/mac_installer/responseExample.xml",
"//chrome/test/data/mac_installer/test-dmg.dmg",
] ]
} }
...@@ -9,23 +9,17 @@ ...@@ -9,23 +9,17 @@
@class Downloader; @class Downloader;
@protocol DownloaderDelegate @protocol DownloaderDelegate
- (void)didDownloadData:(double)downloadProgressPercentage; - (void)downloader:(Downloader*)download percentProgress:(double)percentage;
- (void)downloader:(Downloader*)download - (void)downloader:(Downloader*)download onSuccess:(NSURL*)diskImageURL;
onDownloadSuccess:(NSURL*)diskImagePath; - (void)downloader:(Downloader*)download onFailure:(NSError*)error;
- (void)downloader:(Downloader*)download
onDownloadFailureWithError:(NSError*)error;
@end @end
@interface Downloader : NSObject<NSURLSessionDownloadDelegate> @interface Downloader : NSObject<NSURLSessionDownloadDelegate>
@property(nonatomic, assign) id<DownloaderDelegate> delegate; @property(nonatomic, assign) id<DownloaderDelegate> delegate;
// Takes an NSData with a response XML from Omaha and writes the latest // Downloads Chrome from |chromeImageURL| to the local hard drive.
// version of chrome to the user's download directory. - (void)downloadChromeImageFrom:(NSURL*)chromeImageURL;
- (void)downloadChromeImageToDownloadsDirectory:(NSURL*)chromeImageURL;
// Returns a path to a user's home download folder.
+ (NSString*)getChromeDownloadFilePath;
@end @end
......
...@@ -10,17 +10,9 @@ ...@@ -10,17 +10,9 @@
@synthesize delegate = delegate_; @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 // Downloads contents of chromeURL to downloads folders and delegates the work
// to the DownloadDelegate class. // to the DownloadDelegate class.
- (void)downloadChromeImageToDownloadsDirectory:(NSURL*)chromeImageURL { - (void)downloadChromeImageFrom:(NSURL*)chromeImageURL {
NSURLSession* session = NSURLSession* session =
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration
defaultSessionConfiguration] defaultSessionConfiguration]
...@@ -38,7 +30,7 @@ ...@@ -38,7 +30,7 @@
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
double downloadProgressPercentage = double downloadProgressPercentage =
(double)totalBytesWritten / totalBytesExpectedToWrite * 100.0; (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. // Delegate method to move downloaded disk image to user's Download directory.
...@@ -46,24 +38,14 @@ ...@@ -46,24 +38,14 @@
downloadTask:(NSURLSessionDownloadTask*)downloadTask downloadTask:(NSURLSessionDownloadTask*)downloadTask
didFinishDownloadingToURL:(NSURL*)location { didFinishDownloadingToURL:(NSURL*)location {
assert([location isFileURL]); assert([location isFileURL]);
NSFileManager* manager = [NSFileManager defaultManager]; [delegate_ downloader:self onSuccess:location];
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];
} }
- (void)URLSession:(NSURLSession*)session - (void)URLSession:(NSURLSession*)session
task:(NSURLSessionTask*)task task:(NSURLSessionTask*)task
didCompleteWithError:(NSError*)error { didCompleteWithError:(NSError*)error {
if (error) { if (error) {
[delegate_ downloader:self onDownloadFailureWithError:error]; [delegate_ downloader:self onFailure:error];
} }
} }
......
...@@ -120,7 +120,13 @@ ...@@ -120,7 +120,13 @@
} }
- (void)updateDownloadProgress:(double)progressPercent { - (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 { - (void)enableLaunchButton {
......
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
typedef NSInteger NSModalResponse; typedef NSInteger NSModalResponse;
@interface NSAlert (ChromeInstallerAdditions) @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 @end
#endif // CHROME_INSTALLER_MAC_APP_NSALERT_CHROMEINSTALLERADDITIONS_H_ #endif // CHROME_INSTALLER_MAC_APP_NSALERT_CHROMEINSTALLERADDITIONS_H_
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
// In the one-button scenario, the button would be just "Quit." In the // 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 // two-button scenario, the first button would allow the user to "Retry" and
// the second button would provide the "Quit" option. // the second button would provide the "Quit" option.
- (NSModalResponse)quitButton { - (NSModalResponse)quitResponse {
return ([[self buttons] count] == 1) ? NSAlertFirstButtonReturn return ([[self buttons] count] == 1) ? NSAlertFirstButtonReturn
: NSAlertSecondButtonReturn; : NSAlertSecondButtonReturn;
} }
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface NSError (ChromeInstallerAdditions) @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 + (NSError*)errorForAlerts:(NSString*)message
withDescription:(NSString*)description withDescription:(NSString*)description
isRecoverable:(BOOL)recoverable; isRecoverable:(BOOL)recoverable;
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
#import "NSError+ChromeInstallerAdditions.h" #import "NSError+ChromeInstallerAdditions.h"
@implementation NSError (ChromeInstallerAdditions) @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 + (NSError*)errorForAlerts:(NSString*)message
withDescription:(NSString*)description withDescription:(NSString*)description
isRecoverable:(BOOL)recoverable { isRecoverable:(BOOL)recoverable {
......
...@@ -7,9 +7,12 @@ ...@@ -7,9 +7,12 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@class OmahaCommunication;
@protocol OmahaCommunicationDelegate @protocol OmahaCommunicationDelegate
- (void)onOmahaSuccessWithURLs:(NSArray*)URLs; - (void)omahaCommunication:(OmahaCommunication*)messenger
- (void)onOmahaFailureWithError:(NSError*)error; onSuccess:(NSArray*)URLs;
- (void)omahaCommunication:(OmahaCommunication*)messenger
onFailure:(NSError*)error;
@end @end
@interface OmahaCommunication : NSObject<NSURLSessionDataDelegate> @interface OmahaCommunication : NSObject<NSURLSessionDataDelegate>
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import "OmahaXMLRequest.h" #import "OmahaXMLRequest.h"
#import "OmahaXMLParser.h" #import "OmahaXMLParser.h"
// TODO: turn this string to a command-line flag
static NSString* const omahaURLPath = static NSString* const omahaURLPath =
@"https://tools.google.com/service/update2"; @"https://tools.google.com/service/update2";
...@@ -35,7 +36,7 @@ static NSString* const omahaURLPath = ...@@ -35,7 +36,7 @@ static NSString* const omahaURLPath =
} }
- (void)fetchDownloadURLs { - (void)fetchDownloadURLs {
// TODO: turn this string to a command-line flag // Forming the request
NSURL* requestURL = [NSURL URLWithString:omahaURLPath]; NSURL* requestURL = [NSURL URLWithString:omahaURLPath];
NSMutableURLRequest* request = NSMutableURLRequest* request =
[NSMutableURLRequest requestWithURL:requestURL]; [NSMutableURLRequest requestWithURL:requestURL];
...@@ -44,6 +45,7 @@ static NSString* const omahaURLPath = ...@@ -44,6 +45,7 @@ static NSString* const omahaURLPath =
[[requestXMLBody_ XMLString] dataUsingEncoding:NSUTF8StringEncoding]; [[requestXMLBody_ XMLString] dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = requestBody; request.HTTPBody = requestBody;
request.HTTPMethod = @"POST"; request.HTTPMethod = @"POST";
// Sending the request
[[[NSURLSession sharedSession] [[[NSURLSession sharedSession]
dataTaskWithRequest:request dataTaskWithRequest:request
completionHandler:^(NSData* data, NSURLResponse* response, completionHandler:^(NSData* data, NSURLResponse* response,
...@@ -56,9 +58,9 @@ static NSString* const omahaURLPath = ...@@ -56,9 +58,9 @@ static NSString* const omahaURLPath =
// parsing error, as the user only needs to know there was a problem // parsing error, as the user only needs to know there was a problem
// talking with the Google Update server. // talking with the Google Update server.
if (error) { if (error) {
[delegate_ onOmahaFailureWithError:error]; [delegate_ omahaCommunication:self onFailure:error];
} else { } else {
[delegate_ onOmahaSuccessWithURLs:completeURLs]; [delegate_ omahaCommunication:self onSuccess:completeURLs];
} }
}] resume]; }] resume];
} }
......
...@@ -9,8 +9,9 @@ ...@@ -9,8 +9,9 @@
@interface OmahaXMLParser : NSObject @interface OmahaXMLParser : NSObject
// Parses an XML document and extracts all the URLs found as well as the // Parses an XML document and extracts the URLs and name of the Chrome DMG from
// filename. Adds each URL into the array chromeIncompleteDownloadURLs_. // Omaha, then returns an array with all the URLs concatenated with the
// filename.
+ (NSArray*)parseXML:(NSData*)omahaResponseXML error:(NSError**)error; + (NSArray*)parseXML:(NSData*)omahaResponseXML error:(NSError**)error;
@end @end
......
...@@ -21,8 +21,6 @@ ...@@ -21,8 +21,6 @@
[parser setDelegate:omahaParser]; [parser setDelegate:omahaParser];
if (![parser parse]) { if (![parser parse]) {
*error = [parser parserError]; *error = [parser parserError];
// TODO: pass up error object to indicate error occurred so
// InstallerWindowController can create custom user error message.
return nil; return nil;
} }
...@@ -34,6 +32,9 @@ ...@@ -34,6 +32,9 @@
} }
if ([completeDownloadURLs count] < 1) { 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]; *error = [NSError errorWithDomain:@"ChromeErrorDomain" code:1 userInfo:nil];
return nil; return nil;
} }
......
...@@ -23,9 +23,7 @@ ...@@ -23,9 +23,7 @@
// user attributes that Omaha actually looks at. The other parameters are useful // user attributes that Omaha actually looks at. The other parameters are useful
// for logging purposes but otherwise not directly used. // for logging purposes but otherwise not directly used.
+ (NSXMLDocument*)createXMLRequestBody { + (NSXMLDocument*)createXMLRequestBody {
// TODO: might be a good idea in the future to add a version# for this // TODO: not hard-code protocol version #?
// installer using [[NSBundle mainBundle]
// objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]
NSString* protocol = @"3.0"; NSString* protocol = @"3.0";
NSString* platform = @"mac"; 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 @@ ...@@ -4,21 +4,24 @@
#import "chrome/installer/mac/app/OmahaXMLRequest.h" #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/mac/scoped_nsobject.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/path_service.h"
#include "base/strings/sys_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace { namespace {
TEST(OmahaXMLRequestTest, CreateReturnsValidXML) { TEST(OmahaXMLRequestTest, CreateReturnsValidXML) {
base::scoped_nsobject<NSXMLDocument> xml_body_( NSXMLDocument* xml_body_ = [OmahaXMLRequest createXMLRequestBody];
[OmahaXMLRequest createXMLRequestBody]);
ASSERT_TRUE(xml_body_); ASSERT_TRUE(xml_body_);
NSString* requestDTDLocation = [[[[NSBundle mainBundle] bundlePath] base::FilePath path;
stringByAppendingPathComponent: PathService::Get(base::DIR_SOURCE_ROOT, &path);
@"../../chrome/installer/mac/app/testing/requestCheck.dtd"] path = path.AppendASCII("chrome/test/data/mac_installer/requestCheck.dtd");
stringByResolvingSymlinksInPath]; NSString* requestDTDLocation = base::SysUTF8ToNSString(path.value());
NSData* requestDTDData = [NSData dataWithContentsOfFile:requestDTDLocation]; NSData* requestDTDData = [NSData dataWithContentsOfFile:requestDTDLocation];
ASSERT_TRUE(requestDTDData); 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