OLD | NEW |
| (Empty) |
1 // Copyright 2009-2010 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 #include "omaha/client/bundle_installer.h" | |
17 #include <atlsafe.h> | |
18 #include "base/scoped_ptr.h" | |
19 #include "omaha/base/debug.h" | |
20 #include "omaha/base/error.h" | |
21 #include "omaha/base/logging.h" | |
22 #include "omaha/base/safe_format.h" | |
23 #include "omaha/base/scoped_ptr_address.h" | |
24 #include "omaha/client/client_utils.h" | |
25 #include "omaha/client/help_url_builder.h" | |
26 #include "omaha/client/resource.h" | |
27 #include "omaha/client/shutdown_events.h" | |
28 #include "omaha/common/const_goopdate.h" | |
29 #include "omaha/common/goopdate_utils.h" | |
30 #include "omaha/common/update3_utils.h" | |
31 #include "goopdate/omaha3_idl.h" | |
32 | |
33 namespace omaha { | |
34 | |
35 namespace internal { | |
36 | |
37 CString GetAppDisplayName(IApp* app) { | |
38 ASSERT1(app); | |
39 | |
40 CComBSTR app_name; | |
41 HRESULT hr = app->get_displayName(&app_name); | |
42 if (SUCCEEDED(hr)) { | |
43 ASSERT1(app_name.Length()); | |
44 if (app_name.Length()) { | |
45 return CString(app_name); | |
46 } | |
47 } else { | |
48 CORE_LOG(LW, (_T("[get_displayName failed][0x%08x]"), hr)); | |
49 } | |
50 return client_utils::GetDefaultApplicationName(); | |
51 } | |
52 | |
53 CString BuildAppNameList(const std::vector<CString>& app_names) { | |
54 ASSERT1(!app_names.empty()); | |
55 | |
56 CString list = app_names[0]; | |
57 for (size_t i = 1; i < app_names.size(); ++i) { | |
58 list.FormatMessage(IDS_APPLICATION_NAME_CONCATENATION, list, app_names[i]); | |
59 } | |
60 | |
61 return list; | |
62 } | |
63 | |
64 CompletionCodes ConvertPostInstallActionToCompletionCode( | |
65 PostInstallAction post_install_action, | |
66 bool is_browser_type_supported, | |
67 bool is_error_state) { | |
68 switch (post_install_action) { | |
69 case POST_INSTALL_ACTION_EXIT_SILENTLY_ON_LAUNCH_COMMAND: | |
70 return COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND; | |
71 | |
72 case POST_INSTALL_ACTION_LAUNCH_COMMAND: | |
73 return COMPLETION_CODE_LAUNCH_COMMAND; | |
74 | |
75 case POST_INSTALL_ACTION_RESTART_BROWSER: | |
76 if (is_browser_type_supported) { | |
77 return COMPLETION_CODE_RESTART_BROWSER; | |
78 } else { | |
79 return COMPLETION_CODE_RESTART_BROWSER_NOTICE_ONLY; | |
80 } | |
81 | |
82 case POST_INSTALL_ACTION_RESTART_ALL_BROWSERS: | |
83 if (is_browser_type_supported) { | |
84 return COMPLETION_CODE_RESTART_ALL_BROWSERS; | |
85 } else { | |
86 return COMPLETION_CODE_RESTART_ALL_BROWSERS_NOTICE_ONLY; | |
87 } | |
88 | |
89 case POST_INSTALL_ACTION_REBOOT: | |
90 // We don't support reboot, always notice_only | |
91 return COMPLETION_CODE_REBOOT_NOTICE_ONLY; | |
92 | |
93 case POST_INSTALL_ACTION_EXIT_SILENTLY: | |
94 return COMPLETION_CODE_EXIT_SILENTLY; | |
95 | |
96 case POST_INSTALL_ACTION_DEFAULT: | |
97 if (is_error_state) { | |
98 return COMPLETION_CODE_ERROR; | |
99 } else { | |
100 return COMPLETION_CODE_SUCCESS; | |
101 } | |
102 | |
103 default: | |
104 ASSERT1(false); | |
105 return COMPLETION_CODE_SUCCESS; | |
106 } | |
107 } | |
108 | |
109 HRESULT GetCompletionInformation(IApp* app, | |
110 CurrentState* current_state, | |
111 AppCompletionInfo* completion_info, | |
112 bool is_browser_type_supported) { | |
113 ASSERT1(app); | |
114 ASSERT1(current_state); | |
115 ASSERT1(completion_info); | |
116 | |
117 *current_state = STATE_INIT; | |
118 | |
119 // If the COM call fails, which may be why we encountered an error, this | |
120 // could be "Google Application", which is weird, especially in the mixed | |
121 // results UI. | |
122 completion_info->display_name = internal::GetAppDisplayName(app); | |
123 | |
124 CComBSTR app_id; | |
125 HRESULT hr = app->get_appId(&app_id); | |
126 if (FAILED(hr)) { | |
127 CORE_LOG(LE, (_T("[get_appId failed][0x%08x]"), hr)); | |
128 return hr; | |
129 } | |
130 | |
131 completion_info->app_id = app_id; | |
132 | |
133 CComPtr<ICurrentState> icurrent_state; | |
134 hr = update3_utils::GetAppCurrentState(app, current_state, &icurrent_state); | |
135 if (FAILED(hr)) { | |
136 CORE_LOG(LE, (_T("[GetNextVersionState failed][0x%08x]"), hr)); | |
137 return hr; | |
138 } | |
139 | |
140 ASSERT1(*current_state == STATE_INSTALL_COMPLETE || | |
141 *current_state == STATE_NO_UPDATE || | |
142 *current_state == STATE_ERROR); | |
143 | |
144 hr = icurrent_state->get_errorCode(&completion_info->error_code); | |
145 if (FAILED(hr)) { | |
146 return hr; | |
147 } | |
148 | |
149 LONG extra_code1 = 0; | |
150 hr = icurrent_state->get_extraCode1(&extra_code1); | |
151 if (FAILED(hr)) { | |
152 return hr; | |
153 } | |
154 completion_info->extra_code1 = static_cast<int>(extra_code1); | |
155 | |
156 CComBSTR message; | |
157 hr = icurrent_state->get_completionMessage(&message); | |
158 if (FAILED(hr)) { | |
159 return hr; | |
160 } | |
161 completion_info->completion_message = message; | |
162 | |
163 LONG installer_result = 0; | |
164 hr = icurrent_state->get_installerResultCode(&installer_result); | |
165 if (FAILED(hr)) { | |
166 return hr; | |
167 } | |
168 completion_info->installer_result_code = installer_result; | |
169 | |
170 VARIANT_BOOL is_canceled = VARIANT_TRUE; | |
171 hr = icurrent_state->get_isCanceled(&is_canceled); | |
172 if (FAILED(hr)) { | |
173 return hr; | |
174 } | |
175 completion_info->is_canceled = (is_canceled == VARIANT_TRUE); | |
176 | |
177 CComBSTR post_install_launch_command_line; | |
178 hr = icurrent_state->get_postInstallLaunchCommandLine( | |
179 &post_install_launch_command_line); | |
180 if (FAILED(hr)) { | |
181 return hr; | |
182 } | |
183 completion_info->post_install_launch_command_line = | |
184 post_install_launch_command_line; | |
185 | |
186 CORE_LOG(L5, (_T("[GetCompletionInformation][%s][state: u][code: 0x%08x]") | |
187 _T("[extra code: %d][message: '%s'][installer: %u]"), | |
188 app_id, *current_state, | |
189 completion_info->error_code, | |
190 completion_info->extra_code1, | |
191 message, installer_result)); | |
192 | |
193 CComBSTR post_install_url; | |
194 hr = icurrent_state->get_postInstallUrl(&post_install_url); | |
195 if (FAILED(hr)) { | |
196 return hr; | |
197 } | |
198 completion_info->post_install_url = post_install_url; | |
199 | |
200 LONG post_install_action = static_cast<LONG>(POST_INSTALL_ACTION_DEFAULT); | |
201 hr = icurrent_state->get_postInstallAction(&post_install_action); | |
202 if (FAILED(hr)) { | |
203 return hr; | |
204 } | |
205 completion_info->completion_code = ConvertPostInstallActionToCompletionCode( | |
206 static_cast<PostInstallAction>(post_install_action), | |
207 is_browser_type_supported, | |
208 *current_state == STATE_ERROR); | |
209 | |
210 ASSERT1(SUCCEEDED(completion_info->error_code) || | |
211 completion_info->installer_result_code == 0 || | |
212 completion_info->error_code == GOOPDATEINSTALL_E_INSTALLER_FAILED); | |
213 | |
214 return S_OK; | |
215 } | |
216 | |
217 void GetAppCompletionMessage(IApp* app, | |
218 AppCompletionInfo* app_info, | |
219 bool is_browser_type_supported) { | |
220 ASSERT1(app); | |
221 ASSERT1(app_info); | |
222 | |
223 CurrentState current_state = STATE_INIT; | |
224 | |
225 HRESULT hr = GetCompletionInformation(app, | |
226 ¤t_state, | |
227 app_info, | |
228 is_browser_type_supported); | |
229 if (FAILED(hr)) { | |
230 CORE_LOG(LW, (_T("[GetCompletionInformation failed][0x%08x]"), hr)); | |
231 // Treat the failure as an app failure. | |
232 app_info->completion_message.FormatMessage( | |
233 IDS_INSTALL_FAILED_WITH_ERROR_CODE, | |
234 hr); | |
235 app_info->error_code = hr; | |
236 app_info->extra_code1 = 0; | |
237 return; | |
238 } | |
239 | |
240 ASSERT1(current_state == STATE_INSTALL_COMPLETE || | |
241 current_state == STATE_NO_UPDATE || | |
242 current_state == STATE_ERROR); | |
243 ASSERT1(!app_info->completion_message.IsEmpty()); | |
244 ASSERT1(current_state != STATE_NO_UPDATE || | |
245 app_info->error_code == S_OK || | |
246 app_info->error_code == GOOPDATE_E_UPDATE_DEFERRED); | |
247 | |
248 app_info->is_noupdate = current_state == STATE_NO_UPDATE; | |
249 | |
250 // If it is not a success or noupdate, we must report a failure in error_code. | |
251 if (current_state != STATE_INSTALL_COMPLETE && | |
252 current_state != STATE_NO_UPDATE) { | |
253 ASSERT1(FAILED(app_info->error_code)); | |
254 if (SUCCEEDED(app_info->error_code)) { | |
255 app_info->error_code = E_FAIL; | |
256 app_info->extra_code1 = 0; | |
257 } | |
258 } | |
259 | |
260 if (!app_info->completion_message.IsEmpty()) { | |
261 return; | |
262 } | |
263 | |
264 ASSERT(false, (_T("There should always be a completion message."))); | |
265 | |
266 // The message is empty. Return a default message. | |
267 if (current_state == STATE_INSTALL_COMPLETE) { | |
268 app_info->completion_message.LoadString( | |
269 IDS_APPLICATION_INSTALLED_SUCCESSFULLY); | |
270 } else if (current_state == STATE_NO_UPDATE) { | |
271 VERIFY1(app_info->completion_message.LoadString(IDS_NO_UPDATE_RESPONSE)); | |
272 } else { | |
273 ASSERT1(current_state == STATE_ERROR); | |
274 app_info->completion_message.FormatMessage( | |
275 IDS_INSTALL_FAILED_WITH_ERROR_CODE, | |
276 E_FAIL); | |
277 } | |
278 } | |
279 | |
280 CString BuildResultStringForApps(uint32 group_name_resource_id, | |
281 const std::vector<CString>& apps) { | |
282 CString app_result_string; | |
283 ASSERT1(!apps.empty()); | |
284 | |
285 // The header (i.e. "Failed:") should be in bold. | |
286 const TCHAR* const kAppListHeaderFormatOpen = _T("<b>"); | |
287 const TCHAR* const kAppListHeaderFormatClose = _T("</b>"); | |
288 | |
289 CString apps_list = internal::BuildAppNameList(apps); | |
290 app_result_string.FormatMessage(group_name_resource_id, | |
291 kAppListHeaderFormatOpen, | |
292 kAppListHeaderFormatClose, | |
293 apps_list); | |
294 return app_result_string; | |
295 } | |
296 | |
297 // TODO(omaha): If we end up leaving is_noupdate in AppCompletionInfo, | |
298 // eliminate is_only_no_update parameter. | |
299 CString GetBundleCompletionMessage( | |
300 const CString& bundle_name, | |
301 const std::vector<AppCompletionInfo>& apps_info, | |
302 bool is_only_no_update, | |
303 bool is_canceled) { | |
304 ASSERT1(!apps_info.empty()); | |
305 // TODO(omaha): Enable this assert if GetUpdateAllAppsBundleName() is | |
306 // changed to a different string. | |
307 // ASSERT(bundle_name != SHORT_COMPANY_NAME _T(" Application"), | |
308 // (_T("Do not pass default bundle name to this function."))); | |
309 | |
310 CString bundle_message; | |
311 | |
312 if (is_only_no_update) { | |
313 VERIFY1(bundle_message.LoadString(IDS_NO_UPDATE_RESPONSE)); | |
314 return bundle_message; | |
315 } | |
316 | |
317 std::vector<CString> succeeded_apps; | |
318 std::vector<CString> failed_apps; | |
319 std::vector<CString> canceled_apps; | |
320 CString first_failure_message; | |
321 for (size_t i = 0; i < apps_info.size(); ++i) { | |
322 const AppCompletionInfo& app_info = apps_info[i]; | |
323 ASSERT1(!app_info.display_name.IsEmpty()); | |
324 ASSERT1(!app_info.completion_message.IsEmpty()); | |
325 | |
326 if (SUCCEEDED(app_info.error_code)) { | |
327 succeeded_apps.push_back(app_info.display_name); | |
328 } else if (app_info.is_canceled) { | |
329 canceled_apps.push_back(app_info.display_name); | |
330 } else { | |
331 failed_apps.push_back(app_info.display_name); | |
332 | |
333 // For now, we only display the first error message when all apps fail. | |
334 // Remember that message. | |
335 if (first_failure_message.IsEmpty()) { | |
336 first_failure_message = app_info.completion_message; | |
337 } | |
338 } | |
339 } | |
340 | |
341 CString canceled_apps_str; | |
342 if (!canceled_apps.empty()) { | |
343 canceled_apps_str = BuildResultStringForApps( | |
344 IDS_BUNDLE_MIXED_RESULTS_CANCELED_APPS, canceled_apps); | |
345 } | |
346 | |
347 CString succeeded_apps_str; | |
348 if (!succeeded_apps.empty()) { | |
349 succeeded_apps_str = BuildResultStringForApps( | |
350 IDS_BUNDLE_MIXED_RESULTS_SUCCEEDED_APPS, succeeded_apps); | |
351 } | |
352 CString failed_apps_str; | |
353 if (!failed_apps.empty()) { | |
354 failed_apps_str = BuildResultStringForApps( | |
355 IDS_BUNDLE_MIXED_RESULTS_FAILED_APPS, failed_apps); | |
356 } | |
357 | |
358 // For mixed results, display the succeeded, failed and canceled app lists on | |
359 // their own lines below the main message with a newline between the message | |
360 // and lists. | |
361 const TCHAR* const kLayoutForTwoGroups = _T("%s\n\n%s\n%s"); | |
362 const TCHAR* const kLayoutForThreeGroups = _T("%s\n\n%s\n%s\n%s"); | |
363 | |
364 if (!failed_apps_str.IsEmpty()) { | |
365 // At least one app fails to install, display a failure message. | |
366 uint32 message_id = | |
367 failed_apps.size() == 1 ? | |
368 IDS_BUNDLE_MIXED_RESULTS_MESSAGE_ONE_FAILURE : | |
369 IDS_BUNDLE_MIXED_RESULTS_MESSAGE_MULTIPLE_FAILURES; | |
370 CString message; | |
371 VERIFY1(message.LoadString(message_id)); | |
372 | |
373 if (!succeeded_apps.empty() && !canceled_apps.empty()) { | |
374 SafeCStringFormat(&bundle_message, kLayoutForThreeGroups, | |
375 message, | |
376 succeeded_apps_str, | |
377 failed_apps_str, | |
378 canceled_apps_str); | |
379 } else if (!succeeded_apps.empty()) { | |
380 SafeCStringFormat(&bundle_message, kLayoutForTwoGroups, | |
381 message, | |
382 succeeded_apps_str, | |
383 failed_apps_str); | |
384 } else if (!canceled_apps.empty()) { | |
385 SafeCStringFormat(&bundle_message, kLayoutForTwoGroups, | |
386 message, | |
387 failed_apps_str, | |
388 canceled_apps_str); | |
389 } else { | |
390 bundle_message = first_failure_message; | |
391 } | |
392 } else if (!canceled_apps_str.IsEmpty()) { | |
393 // No failed app, but some are canceled. | |
394 if (!succeeded_apps_str.IsEmpty()) { | |
395 CString message; | |
396 VERIFY1(message.LoadString( | |
397 IDS_BUNDLE_INSTALLED_SUCCESSFULLY_AFTER_CANCEL)); | |
398 SafeCStringFormat(&bundle_message, kLayoutForTwoGroups, | |
399 message, | |
400 succeeded_apps_str, | |
401 canceled_apps_str); | |
402 } else { | |
403 // Only canceled app, no UI will be displayed. | |
404 VERIFY1(bundle_message.LoadString(IDS_CANCELED)); | |
405 } | |
406 } else { | |
407 // All successes. Display a client-specific completion message that includes | |
408 // the bundle name. | |
409 // There is no special handling of apps with noupdate. | |
410 ASSERT1(succeeded_apps.size() == apps_info.size()); | |
411 ASSERT1(first_failure_message.IsEmpty()); | |
412 if (is_canceled) { | |
413 VERIFY1(bundle_message.LoadString( | |
414 IDS_BUNDLE_INSTALLED_SUCCESSFULLY_AFTER_CANCEL)); | |
415 } else if (bundle_name.IsEmpty()) { | |
416 VERIFY1( | |
417 bundle_message.LoadString(IDS_APPLICATION_INSTALLED_SUCCESSFULLY)); | |
418 } else { | |
419 bundle_message.FormatMessage(IDS_BUNDLE_INSTALLED_SUCCESSFULLY, | |
420 bundle_name); | |
421 } | |
422 } | |
423 | |
424 ASSERT1(!bundle_message.IsEmpty()); | |
425 return bundle_message; | |
426 } | |
427 | |
428 } // namespace internal | |
429 | |
430 BundleInstaller::BundleInstaller(HelpUrlBuilder* help_url_builder, | |
431 bool is_update_all_apps, | |
432 bool is_update_check_only, | |
433 bool is_browser_type_supported) | |
434 : help_url_builder_(help_url_builder), | |
435 observer_(NULL), | |
436 parent_window_(NULL), | |
437 state_(kInit), | |
438 result_(E_UNEXPECTED), | |
439 is_canceled_(false), | |
440 is_update_all_apps_(is_update_all_apps), | |
441 is_update_check_only_(is_update_check_only), | |
442 is_browser_type_supported_(is_browser_type_supported) { | |
443 } | |
444 | |
445 BundleInstaller::~BundleInstaller() { | |
446 Uninitialize(); | |
447 } | |
448 | |
449 LRESULT BundleInstaller::OnTimer(UINT msg, | |
450 WPARAM wparam, | |
451 LPARAM, | |
452 BOOL& handled) { // NOLINT | |
453 VERIFY1(msg == WM_TIMER); | |
454 VERIFY1(wparam == kPollingTimerId); | |
455 | |
456 // set_observer() must be called before starting the message loop. | |
457 ASSERT1(observer_); | |
458 | |
459 if (!PollServer()) { | |
460 CORE_LOG(L1, (_T("[BundleInstaller::OnTimer][Stopping polling timer]"))); | |
461 | |
462 // Ignore return value. KillTimer does not remove WM_TIMER messages already | |
463 // posted to the message queue. | |
464 KillTimer(kPollingTimerId); | |
465 } | |
466 | |
467 handled = true; | |
468 return 0; | |
469 } | |
470 | |
471 HRESULT BundleInstaller::Initialize() { | |
472 CORE_LOG(L3, (_T("[BundleInstaller::Initialize]"))); | |
473 | |
474 // Create a message-only window for the timer. It is not visible, | |
475 // has no z-order, cannot be enumerated, and does not receive broadcast | |
476 // messages. The window simply dispatches messages. | |
477 const TCHAR kWndName[] = _T("{139455DE-14E2-4d54-93B5-9E6ADDC04B4E}"); | |
478 if (!Create(HWND_MESSAGE, NULL, kWndName)) { | |
479 return HRESULTFromLastError(); | |
480 } | |
481 | |
482 if (!SetTimer(kPollingTimerId, kPollingTimerPeriodMs)) { | |
483 return HRESULTFromLastError(); | |
484 } | |
485 | |
486 return S_OK; | |
487 } | |
488 | |
489 void BundleInstaller::Uninitialize() { | |
490 if (IsWindow()) { | |
491 // This may fail if it was already killed when the bundle completed. | |
492 KillTimer(kPollingTimerId); | |
493 | |
494 DestroyWindow(); | |
495 } | |
496 } | |
497 | |
498 void BundleInstaller::SetBundleParentWindow(HWND parent_window) { | |
499 ASSERT1(parent_window); | |
500 parent_window_ = parent_window; | |
501 } | |
502 | |
503 HRESULT BundleInstaller::ListenToShutdownEvent(bool is_machine) { | |
504 ASSERT1(!shutdown_callback_.get()); | |
505 HRESULT hr = ShutdownEvents::CreateShutdownHandler( | |
506 is_machine, this, address(shutdown_callback_)); | |
507 if (FAILED(hr)) { | |
508 CORE_LOG(LE, (_T("CreateShutdownHandler failed][0x%08x]"), hr)); | |
509 } | |
510 | |
511 return hr; | |
512 } | |
513 | |
514 void BundleInstaller::StopListenToShutdownEvent(bool is_machine) { | |
515 UNREFERENCED_PARAMETER(is_machine); | |
516 shutdown_callback_.reset(); | |
517 } | |
518 | |
519 HRESULT BundleInstaller::InstallBundle(bool is_machine, | |
520 bool listen_to_shutdown_event, | |
521 IAppBundle* app_bundle, | |
522 InstallProgressObserver* observer) { | |
523 ASSERT1(app_bundle); | |
524 ASSERT1(!app_bundle_); | |
525 app_bundle_.Attach(app_bundle); | |
526 app_bundle_->put_parentHWND(reinterpret_cast<ULONG_PTR>(parent_window_)); | |
527 | |
528 observer_ = observer; | |
529 | |
530 if (listen_to_shutdown_event) { | |
531 ListenToShutdownEvent(is_machine); | |
532 } | |
533 | |
534 _pAtlModule->Lock(); | |
535 | |
536 message_loop_.Run(); | |
537 CORE_LOG(L2, (_T("[message_loop_.Run() returned]"))); | |
538 | |
539 if (listen_to_shutdown_event) { | |
540 StopListenToShutdownEvent(is_machine); | |
541 } | |
542 | |
543 observer_ = NULL; | |
544 | |
545 // Installer should not hold any reference to app bundle after installation. | |
546 ASSERT1(!app_bundle_); | |
547 | |
548 CORE_LOG(L1, (_T("InstallBundle returning][0x%08x]"), result())); | |
549 return result(); | |
550 } | |
551 | |
552 // Shutdown() is called from a thread in the OS threadpool. The PostMessage | |
553 // marshals the call over to the UI thread, which is where DoClose needs to be | |
554 // (and is) called from. | |
555 LRESULT BundleInstaller::OnClose(UINT, | |
556 WPARAM, | |
557 LPARAM, | |
558 BOOL& handled) { // NOLINT | |
559 CORE_LOG(L3, (_T("[BundleInstaller::OnClose]"))); | |
560 | |
561 DoClose(); | |
562 handled = true; | |
563 return 0; | |
564 } | |
565 | |
566 // Assumes that we can call OnComplete multiple times. | |
567 void BundleInstaller::DoClose() { | |
568 CORE_LOG(L1, (_T("[BundleInstaller::DoClose]"))); | |
569 if (kComplete != state_) { | |
570 CORE_LOG(L1, | |
571 (_T("[UI closed before install completed. Likely canceled.]"))); | |
572 DoCancel(); | |
573 } | |
574 } | |
575 | |
576 void BundleInstaller::DoExit() { | |
577 CORE_LOG(L1, (_T("[BundleInstaller::DoExit]"))); | |
578 ASSERT(state_ == kComplete, (_T("[State not complete yet, cannot exit!]"))); | |
579 | |
580 _pAtlModule->Unlock(); | |
581 } | |
582 | |
583 void BundleInstaller::DoCancel() { | |
584 CancelBundle(); | |
585 is_canceled_ = true; | |
586 } | |
587 | |
588 // Unless a catastrophic/unrecoverable error occurs, bundle processing should | |
589 // continue, resulting in NotifyBundleInstallComplete() being called and S_OK | |
590 // being returned up the callstack. | |
591 // | |
592 // The following classes of errors can be returned by DoPollServer(). | |
593 // * Errors returned by methods in this class (e.g. E_FAIL). | |
594 // The state_ may already have been set to kComplete | |
595 // * (e.g. GOOPDATE_E_NO_UPDATE_RESPONSE) or not (e.g. E_FAIL). | |
596 // * Errors returned by utility methods (e.g. by goopdate_utils methods). | |
597 // * COM errors returned due to API or sever failures. | |
598 // * Errors returned by COM methods (e.g. by install()). | |
599 // | |
600 // Specifically note that app errors are not returned up the call stack. These | |
601 // are handled and reported by NotifyBundleInstallComplete(). | |
602 // | |
603 // Thus, for all errors returned by DoPollServer() except those returned by COM | |
604 // methods (not property methods), the client knows the error description. | |
605 // TODO(omaha3): What should we do for the COM method errors? There is currently | |
606 // no error API for the bundle. Also, errors returned by these calls may not | |
607 // be Omaha-specific errors. They could be COM errors (e.g. server unavailable), | |
608 // in which case calling a COM method would not work. | |
609 // | |
610 // DoPollServer() handles some client-side errors | |
611 // (e.g. GOOPDATE_E_NO_UPDATE_RESPONSE) by setting state_ to kComplete. All | |
612 // other errors must be handled by calling Complete(). | |
613 // In all error cases, the bundle must be canceled. | |
614 bool BundleInstaller::PollServer() { | |
615 HRESULT hr = DoPollServer(); | |
616 | |
617 // Handle the error unless it has already been handled. | |
618 if (FAILED(hr)) { | |
619 CORE_LOG(LE, (_T("[DoPollServer failed][0x%08x][%d]"), hr, state_)); | |
620 | |
621 CancelBundle(); | |
622 | |
623 if (state_ != kComplete) { | |
624 CString message; | |
625 message.FormatMessage(IDS_INSTALL_FAILED_WITH_ERROR_CODE, hr); | |
626 BundleCompletionInfo bundle_info(COMPLETION_CODE_ERROR, hr, message); | |
627 Complete(bundle_info); | |
628 } | |
629 } | |
630 | |
631 return state_ != kComplete; | |
632 } | |
633 | |
634 HRESULT BundleInstaller::result() { | |
635 ASSERT1(kComplete == state_); | |
636 return result_; | |
637 } | |
638 | |
639 // Polls the server for the state of the job and updates the UI. | |
640 // Not thread safe. There should only be one installation per process. Do we | |
641 // need to worry about multiple WM_TIMER events at the same time or does the | |
642 // message loop ensure this doesn't happen? | |
643 HRESULT BundleInstaller::DoPollServer() { | |
644 CORE_LOG(L3, (_T("[BundleInstaller::DoPollServer][%u]"), state_)); | |
645 ASSERT1(observer_); | |
646 switch (state_) { | |
647 case kInit: | |
648 return HandleInitState(); | |
649 case kProcessing: | |
650 return HandleProcessingState(); | |
651 case kComplete: | |
652 return S_OK; | |
653 default: | |
654 ASSERT1(false); | |
655 return E_FAIL; | |
656 } | |
657 } | |
658 | |
659 // Checks whether the update check is complete, and if so, gets the number of | |
660 // apps with updates available. | |
661 HRESULT BundleInstaller::HandleUpdateAvailable() { | |
662 CORE_LOG(L3, (_T("[BundleInstaller::HandleUpdateAvailable]"))); | |
663 ASSERT1(!apps_.empty()); | |
664 ASSERT1(app_bundle_); | |
665 | |
666 if (is_update_all_apps_) { | |
667 // Nothing to do. The bundle will automatically continue on to the download. | |
668 return S_OK; | |
669 } | |
670 | |
671 // The bundle will not automatically continue. Initiate the download and | |
672 // install if appropriate. | |
673 | |
674 VARIANT_BOOL is_busy = VARIANT_TRUE; | |
675 HRESULT hr = app_bundle_->isBusy(&is_busy); | |
676 if (FAILED(hr)) { | |
677 return hr; | |
678 } | |
679 | |
680 if (is_busy) { | |
681 // An update is available, but other apps may still be being processed. | |
682 // Wait until bundle is not busy to indicate all apps have been processed. | |
683 return S_OK; | |
684 } | |
685 | |
686 // The only purpose of this call now is to call NotifyUpdateAvailable(). | |
687 int num_updates = 0; | |
688 hr = HandleUpdateCheckResults(&num_updates); | |
689 if (FAILED(hr)) { | |
690 return hr; | |
691 } | |
692 CORE_LOG(L2, (_T("[Update check complete][updates: %d]"), num_updates)); | |
693 ASSERT1(num_updates); | |
694 | |
695 if (is_update_check_only_) { | |
696 return NotifyBundleUpdateCheckOnlyComplete(); | |
697 } | |
698 | |
699 // TODO(omaha): Do we handle an unexpected number of apps correctly? | |
700 // (i.e. apps_.size() != num_updates) | |
701 // This includes one of n apps reporting no update during an install. | |
702 | |
703 return app_bundle_->install(); | |
704 } | |
705 | |
706 // Populates apps_. This must be done here because updateAllApps() adds apps | |
707 // to app_bundle_. | |
708 HRESULT BundleInstaller::HandleInitState() { | |
709 CORE_LOG(L3, (_T("[BundleInstaller::HandleInitState]"))); | |
710 ASSERT1(observer_); | |
711 ASSERT1(app_bundle_); | |
712 | |
713 state_ = kProcessing; | |
714 HRESULT hr = is_update_all_apps_ ? | |
715 app_bundle_->updateAllApps() : | |
716 app_bundle_->checkForUpdate(); | |
717 if (FAILED(hr)) { | |
718 return hr; | |
719 } | |
720 observer_->OnCheckingForUpdate(); | |
721 | |
722 long count = 0; // NOLINT | |
723 hr = app_bundle_->get_Count(&count); | |
724 if (FAILED(hr)) { | |
725 return hr; | |
726 } | |
727 ASSERT1(count > 0); | |
728 | |
729 for (long i = 0; i != count; ++i) { // NOLINT | |
730 CComPtr<IApp> app; | |
731 hr = update3_utils::GetApp(app_bundle_, i, &app); | |
732 if (FAILED(hr)) { | |
733 return hr; | |
734 } | |
735 apps_.push_back(AdaptIApp(app)); | |
736 } | |
737 | |
738 ASSERT1(!apps_.empty()); | |
739 return S_OK; | |
740 } | |
741 | |
742 // Iterates through the apps until it finds one in a non-terminal state. | |
743 // If all apps are in a terminal state, calls NotifyBundleInstallComplete(). | |
744 // Assumes that apps are processed serially in order. If this changes, we need | |
745 // to change the algorithm to avoid a bad UI experience. | |
746 // TODO(omaha): For things like creating a log that might be visible in the UI, | |
747 // we would need to guarantee that we always report each major state for each | |
748 // app. This would also require changing this algorithm. In addition, the COM | |
749 // server would need to return information for all previous AppStates | |
750 // (i.e. download progress while in the install phase). | |
751 HRESULT BundleInstaller::HandleProcessingState() { | |
752 CORE_LOG(L3, (_T("[BundleInstaller::HandleProcessingState]"))); | |
753 ASSERT1(observer_); | |
754 ASSERT1(!apps_.empty()); | |
755 | |
756 const ComPtrIApp* app_to_process = NULL; | |
757 | |
758 for (size_t i = 0; i < apps_.size(); ++i) { | |
759 CurrentState current_state = STATE_INIT; | |
760 CComPtr<ICurrentState> icurrent_state; | |
761 const ComPtrIApp& app = apps_[i]; | |
762 HRESULT hr = update3_utils::GetAppCurrentState(app, | |
763 ¤t_state, | |
764 &icurrent_state); | |
765 if (FAILED(hr)) { | |
766 CORE_LOG(LE, (_T("[GetNextVersionState failed][0x%08x]"), hr)); | |
767 return hr; | |
768 } | |
769 | |
770 switch (current_state) { | |
771 case STATE_INSTALL_COMPLETE: | |
772 case STATE_NO_UPDATE: | |
773 case STATE_ERROR: | |
774 // Terminal state - nothing to do for this app. Check the next app. | |
775 continue; | |
776 case STATE_WAITING_TO_CHECK_FOR_UPDATE: | |
777 case STATE_CHECKING_FOR_UPDATE: | |
778 return S_OK; | |
779 case STATE_UPDATE_AVAILABLE: | |
780 return HandleUpdateAvailable(); | |
781 case STATE_WAITING_TO_DOWNLOAD: | |
782 observer_->OnWaitingToDownload(internal::GetAppDisplayName(app)); | |
783 return S_OK; | |
784 case STATE_RETRYING_DOWNLOAD: | |
785 ASSERT(false, (_T("Unsupported"))); | |
786 return S_OK; // Keep checking in order to be forwards compatible. | |
787 case STATE_DOWNLOADING: | |
788 case STATE_DOWNLOAD_COMPLETE: | |
789 case STATE_EXTRACTING: | |
790 case STATE_APPLYING_DIFFERENTIAL_PATCH: | |
791 case STATE_READY_TO_INSTALL: | |
792 return NotifyDownloadProgress(app, icurrent_state); | |
793 case STATE_WAITING_TO_INSTALL: | |
794 return NotifyWaitingToInstall(app); | |
795 case STATE_INSTALLING: | |
796 return NotifyInstallProgress(app, icurrent_state); | |
797 case STATE_PAUSED: | |
798 ASSERT(false, (_T("Unsupported"))); | |
799 return S_OK; // Keep checking in order to be forwards compatible. | |
800 case STATE_INIT: | |
801 default: | |
802 ASSERT1(false); | |
803 return S_OK; // Keep checking in order to be forwards compatible. | |
804 // Cannot support new terminal states, though. | |
805 } | |
806 } | |
807 | |
808 // No apps were in non-terminal states. The bundle may still be busy, though, | |
809 // because app states are updated separately from the bundle state. | |
810 // TODO(omaha): Should we wait for the bundle to complete? If not, we may need | |
811 // to add appropriate waits and checks for any completion UI bundle actions | |
812 // that require that the AppBundle is not busy. | |
813 | |
814 return NotifyBundleInstallComplete(); | |
815 } | |
816 | |
817 HRESULT BundleInstaller::NotifyUpdateAvailable(IApp* app) { | |
818 CORE_LOG(L3, (_T("[BundleInstaller::NotifyUpdateAvailable]"))); | |
819 ASSERT1(app); | |
820 ASSERT1(observer_); | |
821 | |
822 CComPtr<IAppVersion> next_version; | |
823 HRESULT hr = update3_utils::GetNextAppVersion(app, &next_version); | |
824 if (FAILED(hr)) { | |
825 return hr; | |
826 } | |
827 | |
828 CComBSTR ver; | |
829 hr = next_version->get_version(&ver); | |
830 if (FAILED(hr)) { | |
831 return hr; | |
832 } | |
833 | |
834 CORE_LOG(L3, (_T("[Next Version Update Available][%s]"), CString(ver))); | |
835 | |
836 // TODO(omaha3): Until we force app teams to provide a version, the string | |
837 // may be empty. | |
838 observer_->OnUpdateAvailable(internal::GetAppDisplayName(app), CString(ver)); | |
839 return S_OK; | |
840 } | |
841 | |
842 HRESULT BundleInstaller::NotifyDownloadProgress(IApp* app, | |
843 ICurrentState* icurrent_state) { | |
844 CORE_LOG(L3, (_T("[BundleInstaller::NotifyDownloadProgress]"))); | |
845 ASSERT1(icurrent_state); | |
846 ASSERT1(observer_); | |
847 | |
848 int time_remaining_ms = kCurrentStateProgressUnknown; | |
849 int percentage = 0; | |
850 time64 next_retry_time = 0; | |
851 GetAppDownloadProgress(icurrent_state, | |
852 &time_remaining_ms, | |
853 &percentage, | |
854 &next_retry_time); | |
855 if (next_retry_time != 0) { | |
856 observer_->OnWaitingRetryDownload(internal::GetAppDisplayName(app), | |
857 next_retry_time); | |
858 } else { | |
859 observer_->OnDownloading(internal::GetAppDisplayName(app), | |
860 time_remaining_ms, | |
861 percentage); | |
862 } | |
863 return S_OK; | |
864 } | |
865 | |
866 // Starts the install unless the UI prevents the install from starting, in which | |
867 // case it remains in the same state to be checked again next cycle. | |
868 HRESULT BundleInstaller::NotifyWaitingToInstall(IApp* app) { | |
869 CORE_LOG(L3, (_T("[BundleInstaller::NotifyWaitingToInstall]"))); | |
870 ASSERT1(app); | |
871 ASSERT1(observer_); | |
872 | |
873 // can_start_install is ignored because download and install are no longer | |
874 // discrete phases. | |
875 bool can_start_install = false; | |
876 observer_->OnWaitingToInstall(internal::GetAppDisplayName(app), | |
877 &can_start_install); | |
878 | |
879 return S_OK; | |
880 } | |
881 | |
882 HRESULT BundleInstaller::NotifyInstallProgress(IApp* app, | |
883 ICurrentState* icurrent_state) { | |
884 CORE_LOG(L3, (_T("[BundleInstaller::NotifyInstallProgress]"))); | |
885 ASSERT1(app); | |
886 ASSERT1(icurrent_state); | |
887 ASSERT1(observer_); | |
888 | |
889 // TODO(omaha3): Get the install progress and time for the current app. | |
890 // Handle kCurrentStateProgressUnknown appropriately. | |
891 UNREFERENCED_PARAMETER(icurrent_state); | |
892 | |
893 observer_->OnInstalling(internal::GetAppDisplayName(app)); | |
894 | |
895 return S_OK; | |
896 } | |
897 | |
898 // Only used by legacy OnDemand. | |
899 HRESULT BundleInstaller::NotifyBundleUpdateCheckOnlyComplete() { | |
900 CORE_LOG(L3, (_T("[BundleInstaller::NotifyBundleUpdateCheckOnlyComplete]"))); | |
901 | |
902 BundleCompletionInfo info(COMPLETION_CODE_SUCCESS, | |
903 S_OK, | |
904 _T("OK")); // Not used by legacy OnDemand. | |
905 | |
906 Complete(info); | |
907 return S_OK; | |
908 } | |
909 | |
910 // Assumes the AppBundle has completed and all apps are in a terminal state. | |
911 // In other words, AppBundle::Cancel does not need to be called. | |
912 // For now, append the completion message(s) from each app. If any app failed, | |
913 // display the failure UI and set this objects result to the app error. | |
914 // TODO(omaha3): Improve the UI for bundles. It does not currently handle | |
915 // restart browser, etc. Nor is the output production-ready as it just appends | |
916 // the completion strings and assumes L2R. We at least need prettier printing, | |
917 // maybe each message on its own line. Also, our error messages are inconsistent | |
918 // in whether they specify the app's name. | |
919 HRESULT BundleInstaller::NotifyBundleInstallComplete() { | |
920 CORE_LOG(L3, (_T("[BundleInstaller::NotifyBundleInstallComplete]"))); | |
921 ASSERT1(!apps_.empty()); | |
922 ASSERT1(app_bundle_); | |
923 | |
924 bool is_only_no_update = true; | |
925 | |
926 std::vector<AppCompletionInfo> apps_info; | |
927 HRESULT bundle_result = S_OK; | |
928 | |
929 // Get the completion info for each app and set the bundle result. | |
930 for (size_t i = 0; i < apps_.size(); ++i) { | |
931 const ComPtrIApp& app = apps_[i]; | |
932 AppCompletionInfo app_info; | |
933 internal::GetAppCompletionMessage(app, | |
934 &app_info, | |
935 is_browser_type_supported_); | |
936 | |
937 CORE_LOG(L1, (_T("[App completion][%Iu][%s]"), i, app_info.ToString())); | |
938 apps_info.push_back(app_info); | |
939 | |
940 is_only_no_update &= app_info.is_noupdate; | |
941 | |
942 ASSERT1(bundle_result == S_OK || FAILED(bundle_result)); | |
943 if (FAILED(app_info.error_code) && SUCCEEDED(bundle_result)) { | |
944 // This is the first app failure. Use this as the result. | |
945 bundle_result = app_info.error_code; | |
946 } | |
947 } | |
948 | |
949 ASSERT1(bundle_result == S_OK || !is_only_no_update); | |
950 | |
951 CComBSTR bundle_name; | |
952 if (FAILED(app_bundle_->get_displayName(&bundle_name))) { | |
953 bundle_name.Empty(); | |
954 } | |
955 | |
956 CString current_bundle_message = internal::GetBundleCompletionMessage( | |
957 CString(bundle_name), | |
958 apps_info, | |
959 is_only_no_update, | |
960 is_canceled_); | |
961 ASSERT1(!current_bundle_message.IsEmpty()); | |
962 CompletionCodes completion_code = COMPLETION_CODE_SUCCESS; | |
963 if (FAILED(bundle_result)) { | |
964 completion_code = COMPLETION_CODE_ERROR; | |
965 } else if (is_canceled_) { | |
966 // User tried to cancel but bundle is installed. | |
967 completion_code = COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL; | |
968 } | |
969 BundleCompletionInfo bundle_info(completion_code, | |
970 bundle_result, | |
971 current_bundle_message); | |
972 bundle_info.apps_info = apps_info; // Copying simplifies the code above. | |
973 | |
974 // The exit code will be non-zero if any app failed to install. | |
975 // TODO(omaha3): What if apps have different settings? It seems anyone calling | |
976 // Omaha would expect to get an error code in this case. We need to make sure | |
977 // this doesn't cause undesirable behavior in the parent process(es). | |
978 Complete(bundle_info); | |
979 return S_OK; | |
980 } | |
981 | |
982 // If an update is available, this method also sets relevant information from | |
983 // the update response. | |
984 // This function, and thus, NotifyUpdateAvailable() is only called if using | |
985 // a phased install where the bundle waits after the update check (in other | |
986 // words, !is_update_all_apps_). Currently, NotifyUpdateAvailable() only does | |
987 // something in the legacy OnDemand case, so this is okay. | |
988 HRESULT BundleInstaller::HandleUpdateCheckResults(int* num_updates) { | |
989 CORE_LOG(L1, (_T("[BundleInstaller::HandleUpdateCheckResults]"))); | |
990 ASSERT1(num_updates); | |
991 | |
992 *num_updates = 0; | |
993 | |
994 for (size_t i = 0; i < apps_.size(); ++i) { | |
995 CurrentState current_state = STATE_INIT; | |
996 CComPtr<ICurrentState> icurrent_state; | |
997 const ComPtrIApp& app = apps_[i]; | |
998 HRESULT hr = update3_utils::GetAppCurrentState(app, | |
999 ¤t_state, | |
1000 &icurrent_state); | |
1001 if (FAILED(hr)) { | |
1002 CORE_LOG(LE, (_T("[GetAppCurrentState failed][0x%08x]"), hr)); | |
1003 return hr; | |
1004 } | |
1005 | |
1006 if (current_state == STATE_NO_UPDATE || current_state == STATE_ERROR) { | |
1007 // Continue to process other apps. | |
1008 // The error information, if applicable, will be reported elsewhere. | |
1009 continue; | |
1010 } else if (current_state != STATE_UPDATE_AVAILABLE) { | |
1011 // The update check may not be complete or may be in an unexpected state. | |
1012 ASSERT1(false); | |
1013 return E_FAIL; | |
1014 } | |
1015 | |
1016 ++*num_updates; | |
1017 VERIFY1(SUCCEEDED(NotifyUpdateAvailable(app))); | |
1018 } | |
1019 | |
1020 return S_OK; | |
1021 } | |
1022 | |
1023 // Assumes icurrent_state represents an app in one of the downloading states. | |
1024 // TODO(omaha3): Since this method does not check the current state, it's | |
1025 // possible to be in Download Complete or later but not report 100%. The server | |
1026 // should ensure it reports 100% and 0 time in these cases. | |
1027 void BundleInstaller::GetAppDownloadProgress(ICurrentState* icurrent_state, | |
1028 int* time_remaining_ms, | |
1029 int* percentage, | |
1030 time64* next_retry_time) { | |
1031 ASSERT1(icurrent_state); | |
1032 ASSERT1(time_remaining_ms); | |
1033 ASSERT1(percentage); | |
1034 | |
1035 LONG local_time_remaining_ms = kCurrentStateProgressUnknown; | |
1036 if (FAILED(icurrent_state->get_downloadTimeRemainingMs( | |
1037 &local_time_remaining_ms))) { | |
1038 local_time_remaining_ms = kCurrentStateProgressUnknown; | |
1039 } | |
1040 | |
1041 int local_percentage = 0; | |
1042 ULONG bytes = 0; | |
1043 ULONG bytes_total = 0; | |
1044 if (FAILED(icurrent_state->get_bytesDownloaded(&bytes)) || | |
1045 FAILED(icurrent_state->get_totalBytesToDownload(&bytes_total))) { | |
1046 local_percentage = 0; | |
1047 } else { | |
1048 ASSERT1(bytes <= bytes_total); | |
1049 local_percentage = static_cast<int>(100ULL * bytes / bytes_total); | |
1050 ASSERT1(0 <= local_percentage && local_percentage <= 100); | |
1051 } | |
1052 | |
1053 | |
1054 ULONGLONG local_next_retry_time = 0; | |
1055 if (FAILED(icurrent_state->get_nextRetryTime(&local_next_retry_time))) { | |
1056 local_next_retry_time = 0; | |
1057 } | |
1058 | |
1059 *time_remaining_ms = local_time_remaining_ms; | |
1060 *percentage = local_percentage; | |
1061 *next_retry_time = static_cast<time64>(local_next_retry_time); | |
1062 | |
1063 // TODO(omaha3): For now, this client treats extracting and patching as part | |
1064 // of downloading. Add UI support for these phases. | |
1065 | |
1066 CORE_LOG(L4, (_T("[AppDownloadProgress]") | |
1067 _T("[bytes %u][bytes_total %u][percentage %d][ms %d]"), | |
1068 bytes, bytes_total, *percentage, *time_remaining_ms)); | |
1069 } | |
1070 | |
1071 void BundleInstaller::CancelBundle() { | |
1072 CORE_LOG(L1, (_T("[BundleInstaller::CancelBundle]"))); | |
1073 if (app_bundle_) { | |
1074 VERIFY1(SUCCEEDED(app_bundle_->stop())); | |
1075 } | |
1076 } | |
1077 | |
1078 // error_code is copied to result_, which is the return code for this object. | |
1079 void BundleInstaller::Complete(const BundleCompletionInfo& bundle_info) { | |
1080 CORE_LOG(L1, (_T("[BundleInstaller::Complete][%s]"), bundle_info.ToString())); | |
1081 ASSERT1(observer_); | |
1082 ASSERT1(!bundle_info.bundle_completion_message.IsEmpty()); | |
1083 | |
1084 CString help_url; | |
1085 if (bundle_info.completion_code == COMPLETION_CODE_ERROR) { | |
1086 std::vector<HelpUrlBuilder::AppResult> app_install_results; | |
1087 for (size_t i = 0; i < bundle_info.apps_info.size(); ++i) { | |
1088 const AppCompletionInfo& info = bundle_info.apps_info[i]; | |
1089 // TODO(omaha3): Pass info.extra_code to HelpUrlBuilder as well so that | |
1090 // the help URL has both extra code and installer result code. | |
1091 app_install_results.push_back( | |
1092 HelpUrlBuilder::AppResult(info.app_id, | |
1093 info.error_code, | |
1094 info.installer_result_code)); | |
1095 } | |
1096 | |
1097 if (help_url_builder_.get()) { | |
1098 VERIFY1(SUCCEEDED(help_url_builder_->BuildUrl(app_install_results, | |
1099 &help_url))); | |
1100 ASSERT1(!help_url.IsEmpty()); | |
1101 } | |
1102 } | |
1103 | |
1104 // Set result_ and state_ before calling OnComplete on the observer. | |
1105 // Otherwise, we end up calling BundleInstaller::Complete() recursively from | |
1106 // DoClose(). | |
1107 result_ = bundle_info.bundle_result; | |
1108 state_ = kComplete; | |
1109 | |
1110 ReleaseAppBundle(); | |
1111 | |
1112 // TODO(omaha3): We need to expose some more items to the observer, such as | |
1113 // install_manifest.install_actions[].success_action. There are a lot of | |
1114 // things we need to expose together: success action, restart browser, | |
1115 // terminate all browsers, url, and maybe others. Let's take | |
1116 // the opportunity to standardize these even if the registry and config APIs | |
1117 // are not ideal (i.e. success_url should not imply an action as it does in | |
1118 // the config). | |
1119 | |
1120 ObserverCompletionInfo observer_info(bundle_info.completion_code); | |
1121 // TODO(omaha3): Consider moving the creation of the bundle completion | |
1122 // message from this class to the observer. | |
1123 observer_info.completion_text = bundle_info.bundle_completion_message; | |
1124 observer_info.help_url = help_url; | |
1125 observer_info.apps_info = bundle_info.apps_info; | |
1126 | |
1127 observer_->OnComplete(observer_info); | |
1128 } | |
1129 | |
1130 // Omaha event pings are sent in AppBundle destructor. Release app_bundle_ and | |
1131 // its related interfaces explicitly so that the pings can be sent sooner. | |
1132 void BundleInstaller::ReleaseAppBundle() { | |
1133 CORE_LOG(L3, (_T("[ReleaseAppBundle]"))); | |
1134 apps_.clear(); | |
1135 app_bundle_ = NULL; | |
1136 } | |
1137 | |
1138 } // namespace omaha | |
OLD | NEW |