| 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 |