OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "AppDelegate.h" | 5 #import "AppDelegate.h" |
6 | 6 |
| 7 #include <Security/Security.h> |
| 8 |
| 9 #import "Downloader.h" |
7 #import "InstallerWindowController.h" | 10 #import "InstallerWindowController.h" |
8 #import "NSError+ChromeInstallerAdditions.h" | 11 #import "NSError+ChromeInstallerAdditions.h" |
9 #import "NSAlert+ChromeInstallerAdditions.h" | 12 #import "NSAlert+ChromeInstallerAdditions.h" |
10 #import "AuthorizedInstall.h" | 13 #import "AuthorizedInstall.h" |
| 14 #import "OmahaCommunication.h" |
| 15 #import "Unpacker.h" |
11 | 16 |
12 @interface NSAlert () | 17 @interface NSAlert () |
13 - (void)beginSheetModalForWindow:(NSWindow*)sheetWindow | 18 - (void)beginSheetModalForWindow:(NSWindow*)sheetWindow |
14 completionHandler: | 19 completionHandler: |
15 (void (^__nullable)(NSModalResponse returnCode))handler; | 20 (void (^__nullable)(NSModalResponse returnCode))handler; |
16 @end | 21 @end |
17 | 22 |
18 @interface AppDelegate ()<OmahaCommunicationDelegate, DownloaderDelegate> { | 23 @interface AppDelegate ()<NSWindowDelegate, |
| 24 OmahaCommunicationDelegate, |
| 25 DownloaderDelegate, |
| 26 UnpackDelegate> { |
19 InstallerWindowController* installerWindowController_; | 27 InstallerWindowController* installerWindowController_; |
20 AuthorizedInstall* authorizedInstall_; | 28 AuthorizedInstall* authorizedInstall_; |
| 29 BOOL preventTermination_; |
21 } | 30 } |
22 @property(strong) NSWindow* window; | 31 @property(strong) NSWindow* window; |
| 32 - (void)exit; |
23 @end | 33 @end |
24 | 34 |
25 @implementation AppDelegate | 35 @implementation AppDelegate |
26 @synthesize window = window_; | 36 @synthesize window = window_; |
27 | 37 |
28 // Sets up the main window and begins the downloading process. | 38 // Sets up the main window and begins the downloading process. |
29 - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { | 39 - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { |
30 // TODO: fix UI not loading until after asking for authorization. | 40 // TODO: fix UI not loading until after asking for authorization. |
| 41 window_.delegate = self; |
31 installerWindowController_ = | 42 installerWindowController_ = |
32 [[InstallerWindowController alloc] initWithWindow:window_]; | 43 [[InstallerWindowController alloc] initWithWindow:window_]; |
33 authorizedInstall_ = [[AuthorizedInstall alloc] init]; | 44 authorizedInstall_ = [[AuthorizedInstall alloc] init]; |
34 if ([authorizedInstall_ loadInstallationTool]) { | 45 if ([authorizedInstall_ loadInstallationTool]) { |
35 [self startDownload]; | 46 [self startDownload]; |
36 } else { | 47 } else { |
37 [self onLoadInstallationToolFailure]; | 48 [self onLoadInstallationToolFailure]; |
38 } | 49 } |
39 } | 50 } |
40 | 51 |
41 - (void)applicationWillTerminate:(NSNotification*)aNotification { | 52 - (void)applicationWillTerminate:(NSNotification*)aNotification { |
42 } | 53 } |
43 | 54 |
44 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { | 55 - (NSApplicationTerminateReply)applicationShouldTerminate: |
| 56 (NSApplication*)sender { |
| 57 return preventTermination_ ? NSTerminateCancel : NSTerminateNow; |
| 58 } |
| 59 |
| 60 // This function effectively takes the place of |
| 61 // applicationShouldTerminateAfterLastWindowClosed: to make sure that the |
| 62 // application does correctly terminate after closing the installer, but does |
| 63 // not terminate when we call orderOut: to hide the installer during its |
| 64 // tear-down steps. |
| 65 - (BOOL)windowShouldClose:(id)sender { |
| 66 [self exit]; |
45 return YES; | 67 return YES; |
46 } | 68 } |
47 | 69 |
| 70 - (void)exit { |
| 71 preventTermination_ = NO; |
| 72 [NSApp terminate:nil]; |
| 73 } |
| 74 |
48 - (void)startDownload { | 75 - (void)startDownload { |
49 [installerWindowController_ updateStatusDescription:@"Initializing..."]; | 76 [installerWindowController_ updateStatusDescription:@"Initializing..."]; |
| 77 |
50 OmahaCommunication* omahaMessenger = [[OmahaCommunication alloc] init]; | 78 OmahaCommunication* omahaMessenger = [[OmahaCommunication alloc] init]; |
51 omahaMessenger.delegate = self; | 79 omahaMessenger.delegate = self; |
52 | |
53 [omahaMessenger fetchDownloadURLs]; | 80 [omahaMessenger fetchDownloadURLs]; |
54 } | 81 } |
55 | 82 |
56 - (void)onOmahaSuccessWithURLs:(NSArray*)URLs { | 83 - (void)onLoadInstallationToolFailure { |
| 84 NSError* loadToolError = [NSError |
| 85 errorForAlerts:@"Could not load installion tool" |
| 86 withDescription: |
| 87 @"Your Chrome Installer may be corrupted. Download and try again." |
| 88 isRecoverable:NO]; |
| 89 [self displayError:loadToolError]; |
| 90 } |
| 91 |
| 92 - (void)omahaCommunication:(OmahaCommunication*)messenger |
| 93 onSuccess:(NSArray*)URLs { |
57 [installerWindowController_ updateStatusDescription:@"Downloading..."]; | 94 [installerWindowController_ updateStatusDescription:@"Downloading..."]; |
| 95 |
58 Downloader* download = [[Downloader alloc] init]; | 96 Downloader* download = [[Downloader alloc] init]; |
59 download.delegate = self; | 97 download.delegate = self; |
60 [download downloadChromeImageToDownloadsDirectory:[URLs firstObject]]; | 98 [download downloadChromeImageFrom:[URLs firstObject]]; |
61 } | 99 } |
62 | 100 |
63 - (void)onOmahaFailureWithError:(NSError*)error { | 101 - (void)omahaCommunication:(OmahaCommunication*)messenger |
| 102 onFailure:(NSError*)error { |
64 NSError* networkError = | 103 NSError* networkError = |
65 [NSError errorForAlerts:@"Network Error" | 104 [NSError errorForAlerts:@"Network Error" |
66 withDescription:@"Could not connect to Chrome server." | 105 withDescription:@"Could not connect to Chrome server." |
67 isRecoverable:YES]; | 106 isRecoverable:YES]; |
68 [self displayError:networkError]; | 107 [self displayError:networkError]; |
69 } | 108 } |
70 | 109 |
71 // Bridge method from Downloader to InstallerWindowController. Allows Downloader | 110 // Bridge method from Downloader to InstallerWindowController. Allows Downloader |
72 // to update the progressbar without having direct access to any UI obejcts. | 111 // to update the progressbar without having direct access to any UI obejcts. |
73 - (void)didDownloadData:(double)downloadProgressPercentage { | 112 - (void)downloader:(Downloader*)download percentProgress:(double)percentage { |
74 [installerWindowController_ | 113 [installerWindowController_ updateDownloadProgress:(double)percentage]; |
75 updateDownloadProgress:(double)downloadProgressPercentage]; | |
76 } | 114 } |
77 | 115 |
78 - (void)downloader:(Downloader*)download | 116 - (void)downloader:(Downloader*)download onSuccess:(NSURL*)diskImageURL { |
79 onDownloadSuccess:(NSURL*)diskImagePath { | 117 [installerWindowController_ updateStatusDescription:@"Installing..."]; |
80 [installerWindowController_ updateStatusDescription:@"Done."]; | |
81 [installerWindowController_ enableLaunchButton]; | 118 [installerWindowController_ enableLaunchButton]; |
82 // TODO: Add unpacking step here and pass the path to the app bundle inside | 119 // TODO: Add unpacking step here and pass the path to the app bundle inside |
83 // the mounted disk image path to startInstall. Currently passing hardcoded | 120 // the mounted disk image path to startInstall. Currently passing hardcoded |
84 // path to preunpacked app bundle. | 121 // path to preunpacked app bundle. |
85 //[authorizedInstall_ | 122 //[authorizedInstall_ |
86 // startInstall:@"$HOME/Downloads/Google Chrome.app"]; | 123 // startInstall:@"$HOME/Downloads/Google Chrome.app"]; |
| 124 |
| 125 Unpacker* unpacker = [[Unpacker alloc] init]; |
| 126 unpacker.delegate = self; |
| 127 [unpacker mountDMGFromURL:diskImageURL]; |
87 } | 128 } |
88 | 129 |
89 - (void)downloader:(Downloader*)download | 130 - (void)downloader:(Downloader*)download onFailure:(NSError*)error { |
90 onDownloadFailureWithError:(NSError*)error { | |
91 NSError* downloadError = | 131 NSError* downloadError = |
92 [NSError errorForAlerts:@"Download Failure" | 132 [NSError errorForAlerts:@"Download Failure" |
93 withDescription:@"Unable to download Google Chrome." | 133 withDescription:@"Unable to download Google Chrome." |
94 isRecoverable:NO]; | 134 isRecoverable:NO]; |
95 [self displayError:downloadError]; | 135 [self displayError:downloadError]; |
96 } | 136 } |
97 | 137 |
98 - (void)onLoadInstallationToolFailure { | 138 - (void)unpacker:(Unpacker*)unpacker onMountSuccess:(NSString*)tempAppPath { |
99 NSError* loadToolError = [NSError | 139 SecStaticCodeRef diskStaticCode; |
100 errorForAlerts:@"Could not load installion tool" | 140 SecRequirementRef diskRequirement; |
101 withDescription: | 141 // TODO: flush out error handling more |
102 @"Your Chrome Installer may be corrupted. Download and try again." | 142 OSStatus oserror; |
103 isRecoverable:NO]; | 143 oserror = SecStaticCodeCreateWithPath( |
104 [self displayError:loadToolError]; | 144 (__bridge CFURLRef)[NSURL fileURLWithPath:tempAppPath isDirectory:NO], |
| 145 kSecCSDefaultFlags, &diskStaticCode); |
| 146 if (oserror != errSecSuccess) |
| 147 NSLog(@"code %d", oserror); |
| 148 // TODO: add in a more specific code sign requirement |
| 149 oserror = |
| 150 SecRequirementCreateWithString((CFStringRef) @"anchor apple generic", |
| 151 kSecCSDefaultFlags, &diskRequirement); |
| 152 if (oserror != errSecSuccess) |
| 153 NSLog(@"requirement %d", oserror); |
| 154 oserror = SecStaticCodeCheckValidity(diskStaticCode, kSecCSDefaultFlags, |
| 155 diskRequirement); |
| 156 if (oserror != errSecSuccess) |
| 157 NSLog(@"static code %d", oserror); |
| 158 |
| 159 // Calling this function will change the progress bar into an indeterminate |
| 160 // one. We won't need to update the progress bar any more after this point. |
| 161 [installerWindowController_ updateDownloadProgress:-1.0]; |
| 162 // By disabling closing the window or quitting, we can tell the user that |
| 163 // closing the application at this point is not a good idea. |
| 164 window_.styleMask &= ~NSClosableWindowMask; |
| 165 preventTermination_ = YES; |
| 166 |
| 167 // TODO: move the below code into AuthorizedInstall |
| 168 NSString* chromeInApplicationsFolder = @"/Applications/Google Chromo.app"; |
| 169 |
| 170 NSError* error = nil; |
| 171 if ([[NSFileManager defaultManager] |
| 172 fileExistsAtPath:chromeInApplicationsFolder]) { |
| 173 [[NSFileManager defaultManager] moveItemAtPath:chromeInApplicationsFolder |
| 174 toPath:tempAppPath |
| 175 error:nil]; |
| 176 } |
| 177 if (![[NSFileManager defaultManager] moveItemAtPath:tempAppPath |
| 178 toPath:chromeInApplicationsFolder |
| 179 error:&error]) { |
| 180 NSLog(@"%@", error); |
| 181 } |
| 182 // TODO: move the above code into AuthorizedInstall |
| 183 |
| 184 [[NSWorkspace sharedWorkspace] |
| 185 launchApplicationAtURL:[NSURL fileURLWithPath:chromeInApplicationsFolder |
| 186 isDirectory:NO] |
| 187 options:NSWorkspaceLaunchDefault |
| 188 configuration:@{} |
| 189 error:&error]; |
| 190 if (error) { |
| 191 NSLog(@"Chrome failed to launch: %@", error); |
| 192 } |
| 193 |
| 194 // Begin teardown stuff! |
| 195 dispatch_async(dispatch_get_main_queue(), ^{ |
| 196 [window_ orderOut:nil]; |
| 197 }); |
| 198 |
| 199 [unpacker unmountDMG]; |
| 200 } |
| 201 |
| 202 - (void)unpacker:(Unpacker*)unpacker onMountFailure:(NSError*)error { |
| 203 NSError* extractError = |
| 204 [NSError errorForAlerts:@"Install Failure" |
| 205 withDescription:@"Unable to add Google Chrome to Applications." |
| 206 isRecoverable:NO]; |
| 207 [self displayError:extractError]; |
| 208 } |
| 209 |
| 210 - (void)unpacker:(Unpacker*)unpacker onUnmountSuccess:(NSString*)mountpath { |
| 211 NSLog(@"we're done here!"); |
| 212 [self exit]; |
| 213 } |
| 214 |
| 215 - (void)unpacker:(Unpacker*)unpacker onUnmountFailure:(NSError*)error { |
| 216 NSLog(@"error unmounting"); |
| 217 // NOTE: since we are not deleting the temporary folder if the unmount fails, |
| 218 // we'll just leave it up to the computer to delete the temporary folder on |
| 219 // its own time, and to unmount the disk during a restart at some point. There |
| 220 // is no other work to be done in the mean time. |
| 221 [self exit]; |
105 } | 222 } |
106 | 223 |
107 // Displays an alert on the main window using the contents of the passed in | 224 // Displays an alert on the main window using the contents of the passed in |
108 // error. | 225 // error. |
109 - (void)displayError:(NSError*)error { | 226 - (void)displayError:(NSError*)error { |
110 NSAlert* alertForUser = [NSAlert alertWithError:error]; | 227 NSAlert* alertForUser = [NSAlert alertWithError:error]; |
111 | |
112 dispatch_async(dispatch_get_main_queue(), ^{ | 228 dispatch_async(dispatch_get_main_queue(), ^{ |
113 [alertForUser beginSheetModalForWindow:window_ | 229 [alertForUser beginSheetModalForWindow:window_ |
114 completionHandler:^(NSModalResponse returnCode) { | 230 completionHandler:^(NSModalResponse returnCode) { |
115 if (returnCode != [alertForUser quitButton]) { | 231 if (returnCode != [alertForUser quitResponse]) { |
116 [self startDownload]; | 232 [self startDownload]; |
117 } else { | 233 } else { |
118 [NSApp terminate:nil]; | 234 [NSApp terminate:nil]; |
119 } | 235 } |
120 }]; | 236 }]; |
121 }); | 237 }); |
122 } | 238 } |
123 | 239 |
124 @end | 240 @end |
OLD | NEW |