Home | History | Annotate | Download | only in app_host
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "apps/app_host/binaries_installer.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/threading/platform_thread.h"
      9 #include "base/win/scoped_bstr.h"
     10 #include "base/win/scoped_com_initializer.h"
     11 #include "base/win/scoped_comptr.h"
     12 #include "google_update/google_update_idl.h"
     13 
     14 
     15 namespace app_host {
     16 
     17 // Helpers --------------------------------------------------------------------
     18 
     19 namespace {
     20 
     21 const wchar_t kAppHostAppId[] = L"{FDA71E6F-AC4C-4a00-8B70-9958A68906BF}";
     22 const wchar_t kBinariesAppId[] = L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}";
     23 const int kInstallationPollingIntervalMs = 50;
     24 
     25 HRESULT CreateInstalledApp(IAppBundle* app_bundle,
     26                            const wchar_t* app_guid,
     27                            IApp** app) {
     28   base::win::ScopedComPtr<IDispatch> idispatch;
     29   HRESULT hr = app_bundle->createInstalledApp(base::win::ScopedBstr(app_guid),
     30                                               idispatch.Receive());
     31   if (FAILED(hr)) {
     32     LOG(ERROR) << "Failed to configure App Bundle: " << hr;
     33     return hr;
     34   }
     35 
     36   base::win::ScopedComPtr<IApp> temp_app;
     37   hr = temp_app.QueryFrom(idispatch);
     38   if (FAILED(hr)) {
     39     LOG(ERROR) << "Unexpected error querying IApp from "
     40                << "IAppBundle->createInstalledApp return value: " << hr;
     41   } else {
     42     *app = temp_app.Detach();
     43   }
     44   return hr;
     45 }
     46 
     47 HRESULT GetAppHostApValue(IGoogleUpdate3* update3,
     48                           IAppBundle* app_bundle,
     49                           BSTR* ap_value) {
     50   base::win::ScopedComPtr<IApp> app;
     51   HRESULT hr = CreateInstalledApp(app_bundle, kAppHostAppId, app.Receive());
     52   if (FAILED(hr))
     53     return hr;
     54 
     55   hr = app->get_ap(ap_value);
     56   if (FAILED(hr))
     57     LOG(ERROR) << "Failed to get the App Launcher AP value.";
     58   return hr;
     59 }
     60 
     61 HRESULT GetCurrentState(IApp* app,
     62                         ICurrentState** current_state,
     63                         CurrentState* state_value) {
     64   base::win::ScopedComPtr<IDispatch> idispatch;
     65   HRESULT hr = app->get_currentState(idispatch.Receive());
     66   if (FAILED(hr)) {
     67     LOG(ERROR) << "Failed to get App Bundle state: " << hr;
     68     return hr;
     69   }
     70 
     71   base::win::ScopedComPtr<ICurrentState> temp_current_state;
     72   hr = temp_current_state.QueryFrom(idispatch);
     73   if (FAILED(hr)) {
     74     LOG(ERROR) << "Unexpected error querying ICurrentState from "
     75                << "IApp::get_currentState return value: " << hr;
     76     return hr;
     77   }
     78 
     79   LONG long_state_value;
     80   hr = temp_current_state->get_stateValue(&long_state_value);
     81   if (SUCCEEDED(hr)) {
     82     *state_value = static_cast<CurrentState>(long_state_value);
     83     *current_state = temp_current_state.Detach();
     84   } else {
     85     LOG(ERROR) << "Failed to get App Bundle state value: " << hr;
     86   }
     87   return hr;
     88 }
     89 
     90 bool CheckIsBusy(IAppBundle* app_bundle, HRESULT* hr) {
     91   VARIANT_BOOL variant_is_busy = VARIANT_TRUE;
     92   *hr = app_bundle->isBusy(&variant_is_busy);
     93   if (FAILED(*hr))
     94     LOG(ERROR) << "Failed to check app_bundle->isBusy: " << *hr;
     95   return (variant_is_busy == VARIANT_TRUE);
     96 }
     97 
     98 void OnUpdateAvailable(IAppBundle* app_bundle, HRESULT* hr) {
     99   // If the app bundle is busy we will just wait some more.
    100   if (CheckIsBusy(app_bundle, hr) || FAILED(*hr))
    101     return;
    102   *hr = app_bundle->download();
    103   if (FAILED(*hr))
    104     LOG(ERROR) << "Failed to initiate bundle download: " << *hr;
    105 }
    106 
    107 void OnReadyToInstall(IAppBundle* app_bundle, HRESULT* hr) {
    108   // If the app bundle is busy we will just wait some more.
    109   if (CheckIsBusy(app_bundle, hr) || FAILED(*hr))
    110     return;
    111   *hr = app_bundle->install();
    112   if (FAILED(*hr))
    113     LOG(ERROR) << "Failed to initiate bundle install: " << *hr;
    114 }
    115 
    116 HRESULT OnError(ICurrentState* current_state) {
    117   LONG error_code;
    118   HRESULT hr = current_state->get_errorCode(&error_code);
    119   if (FAILED(hr)) {
    120     LOG(ERROR) << "Failed to retrieve bundle error code: " << hr;
    121     return hr;
    122   }
    123 
    124   base::win::ScopedBstr completion_message;
    125   HRESULT completion_message_hr =
    126     current_state->get_completionMessage(completion_message.Receive());
    127   if (FAILED(completion_message_hr)) {
    128     LOG(ERROR) << "Bundle installation failed with error " << error_code
    129                << ". Error message retrieval failed with error: "
    130                << completion_message_hr;
    131   } else {
    132     LOG(ERROR) << "Bundle installation failed with error " << error_code << ": "
    133                << completion_message;
    134   }
    135   return error_code;
    136 }
    137 
    138 HRESULT CreateGoogleUpdate3(IGoogleUpdate3** update3) {
    139   base::win::ScopedComPtr<IGoogleUpdate3> temp_update3;
    140   HRESULT hr = temp_update3.CreateInstance(CLSID_GoogleUpdate3UserClass);
    141   if (SUCCEEDED(hr)) {
    142     *update3 = temp_update3.Detach();
    143   } else {
    144     // TODO(erikwright): Try in-proc to support running elevated? According
    145     // to update3_utils.cc (CreateGoogleUpdate3UserClass):
    146     // The primary reason for the LocalServer activation failing on Vista/Win7
    147     // is that COM does not look at HKCU registration when the code is running
    148     // elevated. We fall back to an in-proc mode. The in-proc mode is limited to
    149     // one install at a time, so we use it only as a backup mechanism.
    150     LOG(ERROR) << "Failed to instantiate GoogleUpdate3: " << hr;
    151   }
    152   return hr;
    153 }
    154 
    155 HRESULT CreateAppBundle(IGoogleUpdate3* update3, IAppBundle** app_bundle) {
    156   base::win::ScopedComPtr<IDispatch> idispatch;
    157   HRESULT hr = update3->createAppBundle(idispatch.Receive());
    158   if (FAILED(hr)) {
    159     LOG(ERROR) << "Failed to createAppBundle: " << hr;
    160     return hr;
    161   }
    162 
    163   base::win::ScopedComPtr<IAppBundle> temp_app_bundle;
    164   hr = temp_app_bundle.QueryFrom(idispatch);
    165   if (FAILED(hr)) {
    166     LOG(ERROR) << "Unexpected error querying IAppBundle from "
    167                << "IGoogleUpdate3->createAppBundle return value: " << hr;
    168     return hr;
    169   }
    170 
    171   hr = temp_app_bundle->initialize();
    172   if (FAILED(hr))
    173     LOG(ERROR) << "Failed to initialize App Bundle: " << hr;
    174   else
    175     *app_bundle = temp_app_bundle.Detach();
    176   return hr;
    177 }
    178 
    179 HRESULT SelectBinariesApValue(IGoogleUpdate3* update3,
    180                               BSTR* ap_value) {
    181   // TODO(erikwright): Uncomment this when we correctly propagate the AP value
    182   // from the system-level binaries when quick-enabling the app host at
    183   // user-level (http://crbug.com/178479).
    184   // base::win::ScopedComPtr<IAppBundle> app_bundle;
    185   // HRESULT hr = CreateAppBundle(update3, app_bundle.Receive());
    186   // if (FAILED(hr))
    187   //   return hr;
    188 
    189   // hr = GetAppHostApValue(update3, app_bundle, ap_value);
    190   // if (SUCCEEDED(hr))
    191   //   return hr;
    192 
    193   // TODO(erikwright): distinguish between AppHost not installed and an
    194   // error in GetAppHostApValue.
    195   // TODO(erikwright): Use stable by default when App Host support is in
    196   // stable.
    197   base::win::ScopedBstr temp_ap_value;
    198   if (temp_ap_value.Allocate(L"2.0-dev-multi-apphost") == NULL) {
    199     LOG(ERROR) << "Unexpected error in ScopedBstr::Allocate.";
    200     return E_FAIL;
    201   }
    202   *ap_value = temp_ap_value.Release();
    203   return S_OK;
    204 }
    205 
    206 HRESULT CreateBinariesIApp(IAppBundle* app_bundle, BSTR ap, IApp** app) {
    207   base::win::ScopedComPtr<IDispatch> idispatch;
    208   HRESULT hr = app_bundle->createApp(base::win::ScopedBstr(kBinariesAppId),
    209                                      idispatch.Receive());
    210   if (FAILED(hr)) {
    211     LOG(ERROR) << "Failed to configure App Bundle: " << hr;
    212     return hr;
    213   }
    214 
    215   base::win::ScopedComPtr<IApp> temp_app;
    216   hr = temp_app.QueryFrom(idispatch);
    217   if (FAILED(hr)) {
    218     LOG(ERROR) << "Unexpected error querying IApp from "
    219                << "IAppBundle->createApp return value: " << hr;
    220     return hr;
    221   }
    222 
    223   hr = temp_app->put_isEulaAccepted(VARIANT_TRUE);
    224   if (FAILED(hr)) {
    225     LOG(ERROR) << "Failed to set 'EULA Accepted': " << hr;
    226     return hr;
    227   }
    228 
    229   hr = temp_app->put_ap(ap);
    230   if (FAILED(hr))
    231     LOG(ERROR) << "Failed to set AP value: " << hr;
    232   else
    233     *app = temp_app.Detach();
    234   return hr;
    235 }
    236 
    237 bool CheckIfDone(IAppBundle* app_bundle, IApp* app, HRESULT* hr) {
    238   base::win::ScopedComPtr<ICurrentState> current_state;
    239   CurrentState state_value;
    240   *hr = GetCurrentState(app, current_state.Receive(), &state_value);
    241   if (FAILED(*hr))
    242     return true;
    243 
    244   switch (state_value) {
    245     case STATE_WAITING_TO_CHECK_FOR_UPDATE:
    246     case STATE_CHECKING_FOR_UPDATE:
    247     case STATE_WAITING_TO_DOWNLOAD:
    248     case STATE_RETRYING_DOWNLOAD:
    249     case STATE_DOWNLOADING:
    250     case STATE_WAITING_TO_INSTALL:
    251     case STATE_INSTALLING:
    252     case STATE_DOWNLOAD_COMPLETE:
    253     case STATE_EXTRACTING:
    254     case STATE_APPLYING_DIFFERENTIAL_PATCH:
    255       // These states will all transition on their own.
    256       return false;
    257 
    258     case STATE_UPDATE_AVAILABLE:
    259       OnUpdateAvailable(app_bundle, hr);
    260       return FAILED(*hr);
    261 
    262     case STATE_READY_TO_INSTALL:
    263       OnReadyToInstall(app_bundle, hr);
    264       return FAILED(*hr);
    265 
    266     case STATE_NO_UPDATE:
    267       LOG(INFO) << "Google Update reports that the binaries are already "
    268                 << "installed and up-to-date.";
    269       return true;
    270 
    271     case STATE_INSTALL_COMPLETE:
    272       return true;
    273 
    274     case STATE_ERROR:
    275       *hr = OnError(current_state);
    276       return FAILED(*hr);
    277 
    278     case STATE_INIT:
    279     case STATE_PAUSED:
    280     default:
    281       LOG(ERROR) << "Unexpected bundle state: " << state_value << ".";
    282       *hr = E_FAIL;
    283       return true;
    284   }
    285 }
    286 
    287 }  // namespace
    288 
    289 
    290 // Globals --------------------------------------------------------------------
    291 
    292 HRESULT InstallBinaries() {
    293   base::win::ScopedCOMInitializer initialize_com;
    294   if (!initialize_com.succeeded()) {
    295     LOG(ERROR) << "COM initialization failed";
    296     return E_FAIL;
    297   }
    298 
    299   base::win::ScopedComPtr<IGoogleUpdate3> update3;
    300   HRESULT hr = CreateGoogleUpdate3(update3.Receive());
    301   if (FAILED(hr))
    302     return hr;
    303 
    304   base::win::ScopedBstr ap_value;
    305   hr = SelectBinariesApValue(update3, ap_value.Receive());
    306   if (FAILED(hr))
    307     return hr;
    308 
    309   base::win::ScopedComPtr<IAppBundle> app_bundle;
    310   hr = CreateAppBundle(update3, app_bundle.Receive());
    311   if (FAILED(hr))
    312     return hr;
    313 
    314   base::win::ScopedComPtr<IApp> app;
    315   hr = CreateBinariesIApp(app_bundle, ap_value, app.Receive());
    316   if (FAILED(hr))
    317     return hr;
    318 
    319   hr = app_bundle->checkForUpdate();
    320   if (FAILED(hr)) {
    321     LOG(ERROR) << "Failed to initiate update check: " << hr;
    322     return hr;
    323   }
    324 
    325   // We rely upon Omaha to eventually time out and transition to a failure
    326   // state.
    327   while (!CheckIfDone(app_bundle, app, &hr)) {
    328     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
    329         kInstallationPollingIntervalMs));
    330   }
    331   return hr;
    332 }
    333 
    334 }  // namespace app_host
    335