Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(117)

Side by Side Diff: client/bundle_installer.cc

Issue 624713003: Keep only base/extractor.[cc|h]. (Closed) Base URL: https://chromium.googlesource.com/external/omaha.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « client/bundle_installer.h ('k') | client/bundle_installer_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 &current_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 &current_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 &current_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
OLDNEW
« no previous file with comments | « client/bundle_installer.h ('k') | client/bundle_installer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698