diff options
author | sorin <sorin@chromium.org> | 2015-04-17 10:15:03 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-17 17:15:46 +0000 |
commit | 9797abaa5b60464ee061ef0b55237e6f7a13d3dd (patch) | |
tree | cf43abf0ef5720ec2390f9dbe967145a1fed2a5b /components/update_client | |
parent | 18131163ea9d46318299081666150a2c26442009 (diff) | |
download | chromium_src-9797abaa5b60464ee061ef0b55237e6f7a13d3dd.zip chromium_src-9797abaa5b60464ee061ef0b55237e6f7a13d3dd.tar.gz chromium_src-9797abaa5b60464ee061ef0b55237e6f7a13d3dd.tar.bz2 |
Implement common code for component and extension updaters.
The changes not under update_client are mechanical.
This change creates a working UpdateClient updater client, which can be reused by both component and extension updaters.
This code is not hooked up yet with any of the updaters above.
Those changes will be committed in the near future by other CLs.
R=waffles,erikwright,agl,dgarrett,jhawkins,brettw
BUG=450337
Committed: https://crrev.com/ba6742df55de47568e3aa438378d844a7439c391
Cr-Commit-Position: refs/heads/master@{#325006}
Review URL: https://codereview.chromium.org/1055903003
Cr-Commit-Position: refs/heads/master@{#325658}
Diffstat (limited to 'components/update_client')
39 files changed, 3563 insertions, 107 deletions
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn index c968b4d..17665721 100644 --- a/components/update_client/BUILD.gn +++ b/components/update_client/BUILD.gn @@ -4,6 +4,14 @@ source_set("update_client") { sources = [ + "action.cc", + "action.h", + "action_update.cc", + "action_update.h", + "action_update_check.cc", + "action_update_check.h", + "action_wait.cc", + "action_wait.h", "background_downloader_win.cc", "background_downloader_win.h", "component_patcher.cc", @@ -20,10 +28,16 @@ source_set("update_client") { "ping_manager.h", "request_sender.cc", "request_sender.h", + "task.h", + "task_update.cc", + "task_update.h", "update_checker.cc", "update_checker.h", "update_client.cc", "update_client.h", + "update_client_internal.h", + "update_engine.cc", + "update_engine.h", "update_query_params.cc", "update_query_params.h", "update_query_params_delegate.cc", @@ -77,6 +91,7 @@ source_set("unit_tests") { "test/ping_manager_unittest.cc", "test/request_sender_unittest.cc", "test/update_checker_unittest.cc", + "test/update_client_unittest.cc", "test/update_response_unittest.cc", ] diff --git a/components/update_client/action.cc b/components/update_client/action.cc new file mode 100644 index 0000000..4a11efe --- /dev/null +++ b/components/update_client/action.cc @@ -0,0 +1,165 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/update_client/action.h" + +#include <algorithm> +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/update_client/action_update.h" +#include "components/update_client/action_wait.h" +#include "components/update_client/configurator.h" +#include "components/update_client/update_engine.h" +#include "components/update_client/utils.h" + +namespace update_client { + +namespace { + +// Returns true if a differential update is available, it has not failed yet, +// and the configuration allows this update. +bool CanTryDiffUpdate(const CrxUpdateItem* update_item, + const scoped_refptr<Configurator>& config) { + return HasDiffUpdate(update_item) && !update_item->diff_update_failed && + config->DeltasEnabled(); +} + +} // namespace + +ActionImpl::ActionImpl() : update_context_(nullptr) { +} + +ActionImpl::~ActionImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ActionImpl::Run(UpdateContext* update_context, Action::Callback callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + update_context_ = update_context; + callback_ = callback; +} + +CrxUpdateItem* ActionImpl::FindUpdateItemById(const std::string& id) const { + DCHECK(thread_checker_.CalledOnValidThread()); + + const auto it(std::find_if( + update_context_->update_items.begin(), + update_context_->update_items.end(), + [&id](const CrxUpdateItem* item) { return item->id == id; })); + + return it != update_context_->update_items.end() ? *it : nullptr; +} + +void ActionImpl::ChangeItemState(CrxUpdateItem* item, CrxUpdateItem::State to) { + DCHECK(thread_checker_.CalledOnValidThread()); + + item->state = to; + + using Events = UpdateClient::Observer::Events; + + const std::string& id(item->id); + switch (to) { + case CrxUpdateItem::State::kChecking: + NotifyObservers(Events::COMPONENT_CHECKING_FOR_UPDATES, id); + break; + case CrxUpdateItem::State::kCanUpdate: + NotifyObservers(Events::COMPONENT_UPDATE_FOUND, id); + break; + case CrxUpdateItem::State::kUpdatingDiff: + case CrxUpdateItem::State::kUpdating: + NotifyObservers(Events::COMPONENT_UPDATE_READY, id); + break; + case CrxUpdateItem::State::kUpdated: + NotifyObservers(Events::COMPONENT_UPDATED, id); + break; + case CrxUpdateItem::State::kUpToDate: + case CrxUpdateItem::State::kNoUpdate: + NotifyObservers(Events::COMPONENT_NOT_UPDATED, id); + break; + case CrxUpdateItem::State::kNew: + case CrxUpdateItem::State::kDownloading: + case CrxUpdateItem::State::kDownloadingDiff: + case CrxUpdateItem::State::kDownloaded: + case CrxUpdateItem::State::kLastStatus: + // No notification for these states. + break; + } +} + +size_t ActionImpl::ChangeAllItemsState(CrxUpdateItem::State from, + CrxUpdateItem::State to) { + DCHECK(thread_checker_.CalledOnValidThread()); + size_t count = 0; + for (auto item : update_context_->update_items) { + if (item->state == from) { + ChangeItemState(item, to); + ++count; + } + } + return count; +} + +void ActionImpl::NotifyObservers(UpdateClient::Observer::Events event, + const std::string& id) { + DCHECK(thread_checker_.CalledOnValidThread()); + update_context_->notify_observers_callback.Run(event, id); +} + +void ActionImpl::UpdateCrx() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!update_context_->queue.empty()); + + const std::string& id = update_context_->queue.front(); + CrxUpdateItem* item = FindUpdateItemById(id); + DCHECK(item); + + scoped_ptr<Action> update_action( + CanTryDiffUpdate(item, update_context_->config) + ? ActionUpdateDiff::Create() + : ActionUpdateFull::Create()); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&Action::Run, base::Unretained(update_action.get()), + update_context_, callback_)); + + update_context_->current_action.reset(update_action.release()); +} + +void ActionImpl::UpdateCrxComplete(CrxUpdateItem* item) { + update_context_->ping_manager->OnUpdateComplete(item); + + update_context_->queue.pop(); + + if (update_context_->queue.empty()) { + UpdateComplete(0); + } else { + // TODO(sorin): the value of timing interval between CRX updates might have + // to be injected at the call site of update_client::UpdateClient::Update. + const int wait_sec = update_context_->config->UpdateDelay(); + + scoped_ptr<ActionWait> action_wait( + new ActionWait(base::TimeDelta::FromSeconds(wait_sec))); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&Action::Run, base::Unretained(action_wait.get()), + update_context_, callback_)); + + update_context_->current_action.reset(action_wait.release()); + } +} + +void ActionImpl::UpdateComplete(int error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + base::Bind(callback_, error)); +} + +} // namespace update_client diff --git a/components/update_client/action.h b/components/update_client/action.h new file mode 100644 index 0000000..d85e868 --- /dev/null +++ b/components/update_client/action.h @@ -0,0 +1,98 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_ACTION_H_ +#define COMPONENTS_UPDATE_CLIENT_ACTION_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/update_client.h" + +namespace update_client { + +class Configurator; +struct CrxUpdateItem; +struct UpdateContext; + +// Any update can be broken down as a sequence of discrete steps, such as +// checking for updates, downloading patches, updating, and waiting between +// successive updates. An action is the smallest unit of work executed by +// the update engine. +// +// Defines an abstract interface for a unit of work, executed by the +// update engine as part of an update. +class Action { + public: + enum class ErrorCategory { + kErrorNone = 0, + kNetworkError, + kUnpackError, + kInstallError, + kServiceError, // Runtime errors which occur in the service itself. + }; + + enum class ServiceError { + ERROR_WAIT = 1, + }; + + using Callback = base::Callback<void(int error)>; + virtual ~Action() {} + + // Runs the code encapsulated by the action. When an action completes, it can + // chain up and transfer the execution flow to another action or it can + // invoke the |callback| when this function has completed and there is nothing + // else to do. + virtual void Run(UpdateContext* update_context, Callback callback) = 0; +}; + +// Provides a reusable implementation of common functions needed by actions. +class ActionImpl { + protected: + ActionImpl(); + ~ActionImpl(); + + void Run(UpdateContext* update_context, Action::Callback callback); + + // Changes the current state of the |item| to the new state |to|. + void ChangeItemState(CrxUpdateItem* item, CrxUpdateItem::State to); + + // Changes the state of all items in |update_context_|. Returns the count + // of items affected by the call. + size_t ChangeAllItemsState(CrxUpdateItem::State from, + CrxUpdateItem::State to); + + // Returns the item associated with the component |id| or nullptr in case + // of errors. + CrxUpdateItem* FindUpdateItemById(const std::string& id) const; + + void NotifyObservers(UpdateClient::Observer::Events event, + const std::string& id); + + // Updates the CRX at the front of the CRX queue in this update context. + void UpdateCrx(); + + // Completes updating the CRX at the front of the queue, and initiates + // the update for the next CRX in the queue, if the queue is not empty. + void UpdateCrxComplete(CrxUpdateItem* item); + + // Called when the updates for all CRXs have finished and the execution + // flow must return back to the update engine. + void UpdateComplete(int error); + + base::ThreadChecker thread_checker_; + + UpdateContext* update_context_; // Not owned by this class. + Action::Callback callback_; + + private: + DISALLOW_COPY_AND_ASSIGN(ActionImpl); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_ACTION_H_ diff --git a/components/update_client/action_update.cc b/components/update_client/action_update.cc new file mode 100644 index 0000000..7aefb43 --- /dev/null +++ b/components/update_client/action_update.cc @@ -0,0 +1,368 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/update_client/action_update.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "base/version.h" +#include "components/update_client/configurator.h" +#include "components/update_client/crx_downloader.h" +#include "components/update_client/update_client.h" +#include "components/update_client/utils.h" + +using std::string; +using std::vector; + +namespace update_client { + +namespace { + +void AppendDownloadMetrics( + const std::vector<CrxDownloader::DownloadMetrics>& source, + std::vector<CrxDownloader::DownloadMetrics>* destination) { + destination->insert(destination->end(), source.begin(), source.end()); +} + +Action::ErrorCategory UnpackerErrorToErrorCategory( + ComponentUnpacker::Error error) { + Action::ErrorCategory error_category = Action::ErrorCategory::kErrorNone; + switch (error) { + case ComponentUnpacker::kNone: + break; + case ComponentUnpacker::kInstallerError: + error_category = Action::ErrorCategory::kInstallError; + break; + default: + error_category = Action::ErrorCategory::kUnpackError; + break; + } + return error_category; +} + +} // namespace + +ActionUpdate::ActionUpdate() { +} + +ActionUpdate::~ActionUpdate() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ActionUpdate::Run(UpdateContext* update_context, Callback callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + ActionImpl::Run(update_context, callback); + + DCHECK(!update_context_->queue.empty()); + + const std::string& id = update_context_->queue.front(); + CrxUpdateItem* item = FindUpdateItemById(id); + DCHECK(item); + + const bool is_background_download(IsBackgroundDownload(item)); + + scoped_ptr<CrxDownloader> crx_downloader( + (*update_context_->crx_downloader_factory)( + is_background_download, update_context_->config->RequestContext(), + update_context_->blocking_task_runner, + update_context_->single_thread_task_runner)); + crx_downloader->set_progress_callback( + base::Bind(&ActionUpdate::DownloadProgress, base::Unretained(this), id)); + update_context_->crx_downloader.reset(crx_downloader.release()); + + const std::vector<GURL> urls(GetUrls(item)); + OnDownloadStart(item); + + update_context_->crx_downloader->StartDownload( + urls, + base::Bind(&ActionUpdate::DownloadComplete, base::Unretained(this), id)); +} + +void ActionUpdate::DownloadProgress( + const std::string& crx_id, + const CrxDownloader::Result& download_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(crx_id == update_context_->queue.front()); + + using Events = UpdateClient::Observer::Events; + NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING, crx_id); +} + +void ActionUpdate::DownloadComplete( + const std::string& crx_id, + const CrxDownloader::Result& download_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(crx_id == update_context_->queue.front()); + + CrxUpdateItem* item = FindUpdateItemById(crx_id); + DCHECK(item); + + AppendDownloadMetrics(update_context_->crx_downloader->download_metrics(), + &item->download_metrics); + + if (download_result.error) { + OnDownloadError(item, download_result); + } else { + OnDownloadSuccess(item, download_result); + update_context_->main_task_runner->PostDelayedTask( + FROM_HERE, base::Bind(&ActionUpdate::Install, base::Unretained(this), + crx_id, download_result.response), + base::TimeDelta::FromMilliseconds( + update_context_->config->StepDelay())); + } + + update_context_->crx_downloader.reset(); +} + +void ActionUpdate::Install(const std::string& crx_id, + const base::FilePath& crx_path) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(crx_id == update_context_->queue.front()); + + CrxUpdateItem* item = FindUpdateItemById(crx_id); + DCHECK(item); + + OnInstallStart(item); + + update_context_->blocking_task_runner->PostTask( + FROM_HERE, + base::Bind(&ActionUpdate::DoInstallOnBlockingTaskRunner, + base::Unretained(this), update_context_, item, crx_path)); +} + +void ActionUpdate::DoInstallOnBlockingTaskRunner( + UpdateContext* update_context, + CrxUpdateItem* item, + const base::FilePath& crx_path) { + update_context->unpacker = new ComponentUnpacker( + item->component.pk_hash, crx_path, item->component.fingerprint, + item->component.installer, + update_context->config->CreateOutOfProcessPatcher(), + update_context->blocking_task_runner); + update_context->unpacker->Unpack( + base::Bind(&ActionUpdate::EndUnpackingOnBlockingTaskRunner, + base::Unretained(this), update_context, item, crx_path)); +} + +void ActionUpdate::EndUnpackingOnBlockingTaskRunner( + UpdateContext* update_context, + CrxUpdateItem* item, + const base::FilePath& crx_path, + ComponentUnpacker::Error error, + int extended_error) { + update_context->unpacker = nullptr; + update_client::DeleteFileAndEmptyParentDirectory(crx_path); + update_context->main_task_runner->PostDelayedTask( + FROM_HERE, + base::Bind(&ActionUpdate::DoneInstalling, base::Unretained(this), + item->id, error, extended_error), + base::TimeDelta::FromMilliseconds(update_context->config->StepDelay())); +} + +void ActionUpdate::DoneInstalling(const std::string& crx_id, + ComponentUnpacker::Error error, + int extended_error) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(crx_id == update_context_->queue.front()); + + CrxUpdateItem* item = FindUpdateItemById(crx_id); + DCHECK(item); + + if (error == ComponentUnpacker::kNone) + OnInstallSuccess(item); + else + OnInstallError(item, error, extended_error); +} + +ActionUpdateDiff::ActionUpdateDiff() { +} + +ActionUpdateDiff::~ActionUpdateDiff() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +scoped_ptr<Action> ActionUpdateDiff::Create() { + return scoped_ptr<Action>(new ActionUpdateDiff); +} + +void ActionUpdateDiff::TryUpdateFull() { + DCHECK(thread_checker_.CalledOnValidThread()); + scoped_ptr<Action> update_action(ActionUpdateFull::Create()); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&Action::Run, base::Unretained(update_action.get()), + update_context_, callback_)); + + update_context_->current_action.reset(update_action.release()); +} + +bool ActionUpdateDiff::IsBackgroundDownload(const CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + return false; +} + +std::vector<GURL> ActionUpdateDiff::GetUrls(const CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + return item->crx_diffurls; +} + +void ActionUpdateDiff::OnDownloadStart(CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kCanUpdate); + + ChangeItemState(item, CrxUpdateItem::State::kDownloadingDiff); +} + +void ActionUpdateDiff::OnDownloadSuccess( + CrxUpdateItem* item, + const CrxDownloader::Result& download_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kDownloadingDiff); + + ChangeItemState(item, CrxUpdateItem::State::kDownloaded); +} + +void ActionUpdateDiff::OnDownloadError( + CrxUpdateItem* item, + const CrxDownloader::Result& download_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kDownloadingDiff); + + item->diff_error_category = static_cast<int>(ErrorCategory::kNetworkError); + item->diff_error_code = download_result.error; + item->diff_update_failed = true; + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&ActionUpdateDiff::TryUpdateFull, base::Unretained(this))); +} + +void ActionUpdateDiff::OnInstallStart(CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + + ChangeItemState(item, CrxUpdateItem::State::kUpdatingDiff); +} + +void ActionUpdateDiff::OnInstallSuccess(CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kUpdatingDiff); + + item->component.version = item->next_version; + item->component.fingerprint = item->next_fp; + ChangeItemState(item, CrxUpdateItem::State::kUpdated); + + UpdateCrxComplete(item); +} + +void ActionUpdateDiff::OnInstallError(CrxUpdateItem* item, + ComponentUnpacker::Error error, + int extended_error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + item->diff_error_category = + static_cast<int>(UnpackerErrorToErrorCategory(error)); + item->diff_error_code = error; + item->diff_extra_code1 = extended_error; + item->diff_update_failed = true; + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&ActionUpdateDiff::TryUpdateFull, base::Unretained(this))); +} + +ActionUpdateFull::ActionUpdateFull() { +} + +ActionUpdateFull::~ActionUpdateFull() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +scoped_ptr<Action> ActionUpdateFull::Create() { + return scoped_ptr<Action>(new ActionUpdateFull); +} + +bool ActionUpdateFull::IsBackgroundDownload(const CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // On demand component updates are always downloaded in foreground. + return !item->on_demand && item->component.allow_background_download && + update_context_->config->UseBackgroundDownloader(); +} + +std::vector<GURL> ActionUpdateFull::GetUrls(const CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + return item->crx_urls; +} + +void ActionUpdateFull::OnDownloadStart(CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kCanUpdate || + item->diff_update_failed); + + ChangeItemState(item, CrxUpdateItem::State::kDownloading); +} + +void ActionUpdateFull::OnDownloadSuccess( + CrxUpdateItem* item, + const CrxDownloader::Result& download_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kDownloading); + + ChangeItemState(item, CrxUpdateItem::State::kDownloaded); +} + +void ActionUpdateFull::OnDownloadError( + CrxUpdateItem* item, + const CrxDownloader::Result& download_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kDownloading); + + item->error_category = static_cast<int>(ErrorCategory::kNetworkError); + item->error_code = download_result.error; + ChangeItemState(item, CrxUpdateItem::State::kNoUpdate); + + UpdateCrxComplete(item); +} + +void ActionUpdateFull::OnInstallStart(CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kDownloaded); + + ChangeItemState(item, CrxUpdateItem::State::kUpdating); +} + +void ActionUpdateFull::OnInstallSuccess(CrxUpdateItem* item) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kUpdating); + + item->component.version = item->next_version; + item->component.fingerprint = item->next_fp; + ChangeItemState(item, CrxUpdateItem::State::kUpdated); + + UpdateCrxComplete(item); +} + +void ActionUpdateFull::OnInstallError(CrxUpdateItem* item, + ComponentUnpacker::Error error, + int extended_error) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(item->state == CrxUpdateItem::State::kUpdating); + + item->error_category = static_cast<int>(UnpackerErrorToErrorCategory(error)); + item->error_code = error; + item->extra_code1 = extended_error; + ChangeItemState(item, CrxUpdateItem::State::kNoUpdate); + + UpdateCrxComplete(item); +} + +} // namespace update_client diff --git a/components/update_client/action_update.h b/components/update_client/action_update.h new file mode 100644 index 0000000..8b33444 --- /dev/null +++ b/components/update_client/action_update.h @@ -0,0 +1,138 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_ACTION_UPDATE_H_ +#define COMPONENTS_UPDATE_CLIENT_ACTION_UPDATE_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/version.h" +#include "components/update_client/action.h" +#include "components/update_client/component_unpacker.h" +#include "components/update_client/update_client.h" +#include "components/update_client/update_engine.h" +#include "url/gurl.h" + +namespace update_client { + +class UpdateChecker; + +// Defines a template method design pattern for ActionUpdate. This class +// implements the common code for updating a CRX using either differential or +// full updates algorithm. +class ActionUpdate : public Action, protected ActionImpl { + public: + ActionUpdate(); + ~ActionUpdate() override; + + // Action overrides. + void Run(UpdateContext* update_context, Callback callback) override; + + private: + virtual bool IsBackgroundDownload(const CrxUpdateItem* item) = 0; + virtual std::vector<GURL> GetUrls(const CrxUpdateItem* item) = 0; + virtual void OnDownloadStart(CrxUpdateItem* item) = 0; + virtual void OnDownloadSuccess( + CrxUpdateItem* item, + const CrxDownloader::Result& download_result) = 0; + virtual void OnDownloadError( + CrxUpdateItem* item, + const CrxDownloader::Result& download_result) = 0; + virtual void OnInstallStart(CrxUpdateItem* item) = 0; + virtual void OnInstallSuccess(CrxUpdateItem* item) = 0; + virtual void OnInstallError(CrxUpdateItem* item, + ComponentUnpacker::Error error, + int extended_error) = 0; + + // Called when progress is being made downloading a CRX. The progress may + // not monotonically increase due to how the CRX downloader switches between + // different downloaders and fallback urls. + void DownloadProgress(const std::string& crx_id, + const CrxDownloader::Result& download_result); + + // Called when the CRX package has been downloaded to a temporary location. + void DownloadComplete(const std::string& crx_id, + const CrxDownloader::Result& download_result); + + void Install(const std::string& crx_id, const base::FilePath& crx_path); + + // TODO(sorin): refactor the public interface of ComponentUnpacker so + // that these calls can run on the main thread. + void DoInstallOnBlockingTaskRunner(UpdateContext* update_context, + CrxUpdateItem* item, + const base::FilePath& crx_path); + + void EndUnpackingOnBlockingTaskRunner(UpdateContext* update_context, + CrxUpdateItem* item, + const base::FilePath& crx_path, + ComponentUnpacker::Error error, + int extended_error); + + void DoneInstalling(const std::string& crx_id, + ComponentUnpacker::Error error, + int extended_error); + + DISALLOW_COPY_AND_ASSIGN(ActionUpdate); +}; + +class ActionUpdateDiff : public ActionUpdate { + public: + static scoped_ptr<Action> Create(); + + private: + ActionUpdateDiff(); + ~ActionUpdateDiff() override; + + void TryUpdateFull(); + + // ActionUpdate overrides. + bool IsBackgroundDownload(const CrxUpdateItem* item) override; + std::vector<GURL> GetUrls(const CrxUpdateItem* item) override; + void OnDownloadStart(CrxUpdateItem* item) override; + void OnDownloadSuccess(CrxUpdateItem* item, + const CrxDownloader::Result& download_result) override; + void OnDownloadError(CrxUpdateItem* item, + const CrxDownloader::Result& download_result) override; + void OnInstallStart(CrxUpdateItem* item) override; + void OnInstallSuccess(CrxUpdateItem* item) override; + void OnInstallError(CrxUpdateItem* item, + ComponentUnpacker::Error error, + int extended_error) override; + + DISALLOW_COPY_AND_ASSIGN(ActionUpdateDiff); +}; + +class ActionUpdateFull : ActionUpdate { + public: + static scoped_ptr<Action> Create(); + + private: + ActionUpdateFull(); + ~ActionUpdateFull() override; + + // ActionUpdate overrides. + bool IsBackgroundDownload(const CrxUpdateItem* item) override; + std::vector<GURL> GetUrls(const CrxUpdateItem* item) override; + void OnDownloadStart(CrxUpdateItem* item) override; + void OnDownloadSuccess(CrxUpdateItem* item, + const CrxDownloader::Result& download_result) override; + void OnDownloadError(CrxUpdateItem* item, + const CrxDownloader::Result& download_result) override; + void OnInstallStart(CrxUpdateItem* item) override; + void OnInstallSuccess(CrxUpdateItem* item) override; + void OnInstallError(CrxUpdateItem* item, + ComponentUnpacker::Error error, + int extended_error) override; + + DISALLOW_COPY_AND_ASSIGN(ActionUpdateFull); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_ACTION_UPDATE_H_ diff --git a/components/update_client/action_update_check.cc b/components/update_client/action_update_check.cc new file mode 100644 index 0000000..c480de8 --- /dev/null +++ b/components/update_client/action_update_check.cc @@ -0,0 +1,204 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/update_client/action_update_check.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/version.h" +#include "components/update_client/action_update.h" +#include "components/update_client/configurator.h" +#include "components/update_client/update_checker.h" +#include "components/update_client/update_client.h" +#include "components/update_client/utils.h" + +using std::string; +using std::vector; + +namespace update_client { + +namespace { + +// Returns true if the |proposed| version is newer than |current| version. +bool IsVersionNewer(const Version& current, const std::string& proposed) { + Version proposed_ver(proposed); + return proposed_ver.IsValid() && current.CompareTo(proposed_ver) < 0; +} + +} // namespace + +ActionUpdateCheck::ActionUpdateCheck( + scoped_ptr<UpdateChecker> update_checker, + const base::Version& browser_version, + const std::string& extra_request_parameters) + : update_checker_(update_checker.Pass()), + browser_version_(browser_version), + extra_request_parameters_(extra_request_parameters) { +} + +ActionUpdateCheck::~ActionUpdateCheck() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ActionUpdateCheck::Run(UpdateContext* update_context, Callback callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + ActionImpl::Run(update_context, callback); + + // Calls out to get the corresponding CrxComponent data for the CRXs in this + // update context. + vector<CrxComponent> crx_components; + update_context_->crx_data_callback.Run(update_context_->ids, &crx_components); + + update_context_->update_items.reserve(crx_components.size()); + + for (size_t i = 0; i != crx_components.size(); ++i) { + scoped_ptr<CrxUpdateItem> item(new CrxUpdateItem); + const CrxComponent& crx_component = crx_components[i]; + + item->id = GetCrxComponentID(crx_component); + item->component = crx_component; + item->last_check = base::Time::Now(); + item->crx_urls.clear(); + item->crx_diffurls.clear(); + item->previous_version = crx_component.version; + item->next_version = Version(); + item->previous_fp = crx_component.fingerprint; + item->next_fp.clear(); + item->diff_update_failed = false; + item->error_category = 0; + item->error_code = 0; + item->extra_code1 = 0; + item->diff_error_category = 0; + item->diff_error_code = 0; + item->diff_extra_code1 = 0; + item->download_metrics.clear(); + + ChangeItemState(item.get(), CrxUpdateItem::State::kChecking); + + update_context_->update_items.push_back(item.release()); + } + + update_checker_->CheckForUpdates( + update_context_->update_items, extra_request_parameters_, + base::Bind(&ActionUpdateCheck::UpdateCheckComplete, + base::Unretained(this))); +} + +void ActionUpdateCheck::UpdateCheckComplete( + const GURL& original_url, + int error, + const std::string& error_message, + const UpdateResponse::Results& results) { + DCHECK(thread_checker_.CalledOnValidThread()); + + VLOG(1) << "Update check completed from: " << original_url.spec(); + + if (!error) + OnUpdateCheckSucceeded(results); + else + OnUpdateCheckFailed(error, error_message); +} + +void ActionUpdateCheck::OnUpdateCheckSucceeded( + const UpdateResponse::Results& results) { + DCHECK(thread_checker_.CalledOnValidThread()); + VLOG(1) << "Update check succeeded."; + std::vector<UpdateResponse::Result>::const_iterator it; + for (it = results.list.begin(); it != results.list.end(); ++it) { + CrxUpdateItem* crx = FindUpdateItemById(it->extension_id); + if (!crx) + continue; + + if (crx->state != CrxUpdateItem::State::kChecking) { + NOTREACHED(); + continue; // Not updating this CRX now. + } + + if (it->manifest.version.empty()) { + // No version means no update available. + ChangeItemState(crx, CrxUpdateItem::State::kNoUpdate); + VLOG(1) << "No update available for CRX: " << crx->id; + continue; + } + + if (!IsVersionNewer(crx->component.version, it->manifest.version)) { + // The CRX is up to date. + ChangeItemState(crx, CrxUpdateItem::State::kUpToDate); + VLOG(1) << "Component already up-to-date: " << crx->id; + continue; + } + + if (!it->manifest.browser_min_version.empty()) { + if (IsVersionNewer(browser_version_, it->manifest.browser_min_version)) { + // The CRX is not compatible with this Chrome version. + VLOG(1) << "Ignoring incompatible CRX: " << crx->id; + ChangeItemState(crx, CrxUpdateItem::State::kNoUpdate); + continue; + } + } + + if (it->manifest.packages.size() != 1) { + // Assume one and only one package per CRX. + VLOG(1) << "Ignoring multiple packages for CRX: " << crx->id; + ChangeItemState(crx, CrxUpdateItem::State::kNoUpdate); + continue; + } + + // Parse the members of the result and queue an upgrade for this CRX. + crx->next_version = Version(it->manifest.version); + + VLOG(1) << "Update found for CRX: " << crx->id; + + const auto& package(it->manifest.packages[0]); + crx->next_fp = package.fingerprint; + + // Resolve the urls by combining the base urls with the package names. + for (size_t i = 0; i != it->crx_urls.size(); ++i) { + const GURL url(it->crx_urls[i].Resolve(package.name)); + if (url.is_valid()) + crx->crx_urls.push_back(url); + } + for (size_t i = 0; i != it->crx_diffurls.size(); ++i) { + const GURL url(it->crx_diffurls[i].Resolve(package.namediff)); + if (url.is_valid()) + crx->crx_diffurls.push_back(url); + } + + ChangeItemState(crx, CrxUpdateItem::State::kCanUpdate); + + update_context_->queue.push(crx->id); + } + + // All components that are not included in the update response are + // considered up to date. + ChangeAllItemsState(CrxUpdateItem::State::kChecking, + CrxUpdateItem::State::kUpToDate); + + if (update_context_->queue.empty()) { + VLOG(1) << "Update check completed but no update is needed."; + UpdateComplete(0); + return; + } + + // Starts the execution flow of updating the CRXs in this context. + UpdateCrx(); +} + +void ActionUpdateCheck::OnUpdateCheckFailed(int error, + const std::string& error_message) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(error); + + VLOG(1) << "Update check failed." << error; + + UpdateComplete(error); +} + +} // namespace update_client diff --git a/components/update_client/action_update_check.h b/components/update_client/action_update_check.h new file mode 100644 index 0000000..b5f1d24 --- /dev/null +++ b/components/update_client/action_update_check.h @@ -0,0 +1,56 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_ACTION_UPDATE_CHECK_H_ +#define COMPONENTS_UPDATE_CLIENT_ACTION_UPDATE_CHECK_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/version.h" +#include "components/update_client/action.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/update_client.h" +#include "components/update_client/update_engine.h" +#include "components/update_client/update_response.h" +#include "url/gurl.h" + +namespace update_client { + +class UpdateChecker; + +// Implements an update check for the CRXs in an update context. +class ActionUpdateCheck : public Action, private ActionImpl { + public: + ActionUpdateCheck(scoped_ptr<UpdateChecker> update_checker, + const base::Version& browser_version, + const std::string& extra_request_parameters); + + ~ActionUpdateCheck() override; + + void Run(UpdateContext* update_context, Callback callback) override; + + private: + void UpdateCheckComplete(const GURL& original_url, + int error, + const std::string& error_message, + const UpdateResponse::Results& results); + + void OnUpdateCheckSucceeded(const UpdateResponse::Results& results); + void OnUpdateCheckFailed(int error, const std::string& error_message); + + scoped_ptr<UpdateChecker> update_checker_; + const base::Version browser_version_; + const std::string extra_request_parameters_; + + DISALLOW_COPY_AND_ASSIGN(ActionUpdateCheck); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_ACTION_UPDATE_CHECK_H_ diff --git a/components/update_client/action_wait.cc b/components/update_client/action_wait.cc new file mode 100644 index 0000000..1e8f8059 --- /dev/null +++ b/components/update_client/action_wait.cc @@ -0,0 +1,61 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/update_client/action_wait.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/update_client/update_engine.h" + +namespace update_client { + +ActionWait::ActionWait(const base::TimeDelta& time_delta) + : time_delta_(time_delta) { +} + +ActionWait::~ActionWait() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ActionWait::Run(UpdateContext* update_context, Callback callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(update_context); + + ActionImpl::Run(update_context, callback); + + const bool result = base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&ActionWait::WaitComplete, base::Unretained(this)), + time_delta_); + + if (!result) { + // Move all items pending updates to the |kNoUpdate| state then return the + // control flow to the update engine, as the updates in this context are + // completed with an error. + while (!update_context->queue.empty()) { + const auto item = FindUpdateItemById(update_context->queue.front()); + if (!item) { + item->error_category = static_cast<int>(ErrorCategory::kServiceError); + item->error_code = static_cast<int>(ServiceError::ERROR_WAIT); + ChangeItemState(item, CrxUpdateItem::State::kNoUpdate); + } else { + NOTREACHED(); + } + update_context->queue.pop(); + } + callback.Run(static_cast<int>(ServiceError::ERROR_WAIT)); + } + + NotifyObservers(UpdateClient::Observer::Events::COMPONENT_WAIT, + update_context_->queue.front()); +} + +void ActionWait::WaitComplete() { + DCHECK(thread_checker_.CalledOnValidThread()); + UpdateCrx(); +} + +} // namespace update_client diff --git a/components/update_client/action_wait.h b/components/update_client/action_wait.h new file mode 100644 index 0000000..d54062b --- /dev/null +++ b/components/update_client/action_wait.h @@ -0,0 +1,36 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_ACTION_WAIT_H_ +#define COMPONENTS_UPDATE_CLIENT_ACTION_WAIT_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/time/time.h" + +#include "components/update_client/action.h" + +namespace update_client { + +// Implements a wait between handling updates for the CRXs in this context. +// To avoid thrashing of local computing resources, updates are applied one +// at a time, with a delay between them. +class ActionWait : public Action, protected ActionImpl { + public: + explicit ActionWait(const base::TimeDelta& time_delta); + ~ActionWait() override; + + void Run(UpdateContext* update_context, Callback callback) override; + + private: + void WaitComplete(); + + const base::TimeDelta time_delta_; + + DISALLOW_COPY_AND_ASSIGN(ActionWait); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_ACTION_WAIT_H_ diff --git a/components/update_client/component_patcher.cc b/components/update_client/component_patcher.cc index 29ce774..0525882 100644 --- a/components/update_client/component_patcher.cc +++ b/components/update_client/component_patcher.cc @@ -43,7 +43,7 @@ base::ListValue* ReadCommands(const base::FilePath& unpack_path) { ComponentPatcher::ComponentPatcher( const base::FilePath& input_dir, const base::FilePath& unpack_dir, - scoped_refptr<ComponentInstaller> installer, + scoped_refptr<CrxInstaller> installer, scoped_refptr<OutOfProcessPatcher> out_of_process_patcher, scoped_refptr<base::SequencedTaskRunner> task_runner) : input_dir_(input_dir), diff --git a/components/update_client/component_patcher.h b/components/update_client/component_patcher.h index 394edfe..23c16c8 100644 --- a/components/update_client/component_patcher.h +++ b/components/update_client/component_patcher.h @@ -40,7 +40,7 @@ class FilePath; namespace update_client { -class ComponentInstaller; +class CrxInstaller; class DeltaUpdateOp; class OutOfProcessPatcher; @@ -61,7 +61,7 @@ class ComponentPatcher : public base::RefCountedThreadSafe<ComponentPatcher> { // out-of-process. ComponentPatcher(const base::FilePath& input_dir, const base::FilePath& unpack_dir, - scoped_refptr<ComponentInstaller> installer, + scoped_refptr<CrxInstaller> installer, scoped_refptr<OutOfProcessPatcher> out_of_process_patcher, scoped_refptr<base::SequencedTaskRunner> task_runner); @@ -86,7 +86,7 @@ class ComponentPatcher : public base::RefCountedThreadSafe<ComponentPatcher> { const base::FilePath input_dir_; const base::FilePath unpack_dir_; - scoped_refptr<ComponentInstaller> installer_; + scoped_refptr<CrxInstaller> installer_; scoped_refptr<OutOfProcessPatcher> out_of_process_patcher_; ComponentUnpacker::Callback callback_; scoped_ptr<base::ListValue> commands_; diff --git a/components/update_client/component_patcher_operation.cc b/components/update_client/component_patcher_operation.cc index c5e7437..e971533 100644 --- a/components/update_client/component_patcher_operation.cc +++ b/components/update_client/component_patcher_operation.cc @@ -64,7 +64,7 @@ void DeltaUpdateOp::Run( const base::DictionaryValue* command_args, const base::FilePath& input_dir, const base::FilePath& unpack_dir, - const scoped_refptr<ComponentInstaller>& installer, + const scoped_refptr<CrxInstaller>& installer, const ComponentUnpacker::Callback& callback, const scoped_refptr<base::SequencedTaskRunner>& task_runner) { callback_ = callback; @@ -141,7 +141,7 @@ DeltaUpdateOpCopy::~DeltaUpdateOpCopy() { ComponentUnpacker::Error DeltaUpdateOpCopy::DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) { + const scoped_refptr<CrxInstaller>& installer) { std::string input_rel_path; if (!command_args->GetString(kInput, &input_rel_path)) return ComponentUnpacker::kDeltaBadCommands; @@ -168,7 +168,7 @@ DeltaUpdateOpCreate::~DeltaUpdateOpCreate() { ComponentUnpacker::Error DeltaUpdateOpCreate::DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) { + const scoped_refptr<CrxInstaller>& installer) { std::string patch_rel_path; if (!command_args->GetString(kPatch, &patch_rel_path)) return ComponentUnpacker::kDeltaBadCommands; @@ -199,7 +199,7 @@ DeltaUpdateOpPatch::~DeltaUpdateOpPatch() { ComponentUnpacker::Error DeltaUpdateOpPatch::DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) { + const scoped_refptr<CrxInstaller>& installer) { std::string patch_rel_path; std::string input_rel_path; if (!command_args->GetString(kPatch, &patch_rel_path) || diff --git a/components/update_client/component_patcher_operation.h b/components/update_client/component_patcher_operation.h index 0771757..1cb09e3 100644 --- a/components/update_client/component_patcher_operation.h +++ b/components/update_client/component_patcher_operation.h @@ -26,7 +26,7 @@ extern const char kCourgette[]; extern const char kInput[]; extern const char kPatch[]; -class ComponentInstaller; +class CrxInstaller; class DeltaUpdateOp : public base::RefCountedThreadSafe<DeltaUpdateOp> { public: @@ -37,7 +37,7 @@ class DeltaUpdateOp : public base::RefCountedThreadSafe<DeltaUpdateOp> { void Run(const base::DictionaryValue* command_args, const base::FilePath& input_dir, const base::FilePath& unpack_dir, - const scoped_refptr<ComponentInstaller>& installer, + const scoped_refptr<CrxInstaller>& installer, const ComponentUnpacker::Callback& callback, const scoped_refptr<base::SequencedTaskRunner>& task_runner); @@ -60,7 +60,7 @@ class DeltaUpdateOp : public base::RefCountedThreadSafe<DeltaUpdateOp> { virtual ComponentUnpacker::Error DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) = 0; + const scoped_refptr<CrxInstaller>& installer) = 0; // Subclasses must override DoRun to actually perform the patching operation. // They must call the provided callback when they have completed their @@ -92,7 +92,7 @@ class DeltaUpdateOpCopy : public DeltaUpdateOp { ComponentUnpacker::Error DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) override; + const scoped_refptr<CrxInstaller>& installer) override; void DoRun(const ComponentUnpacker::Callback& callback) override; @@ -116,7 +116,7 @@ class DeltaUpdateOpCreate : public DeltaUpdateOp { ComponentUnpacker::Error DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) override; + const scoped_refptr<CrxInstaller>& installer) override; void DoRun(const ComponentUnpacker::Callback& callback) override; @@ -160,7 +160,7 @@ class DeltaUpdateOpPatch : public DeltaUpdateOp { ComponentUnpacker::Error DoParseArguments( const base::DictionaryValue* command_args, const base::FilePath& input_dir, - const scoped_refptr<ComponentInstaller>& installer) override; + const scoped_refptr<CrxInstaller>& installer) override; void DoRun(const ComponentUnpacker::Callback& callback) override; diff --git a/components/update_client/component_unpacker.cc b/components/update_client/component_unpacker.cc index efde910..d43313d 100644 --- a/components/update_client/component_unpacker.cc +++ b/components/update_client/component_unpacker.cc @@ -102,7 +102,7 @@ ComponentUnpacker::ComponentUnpacker( const std::vector<uint8_t>& pk_hash, const base::FilePath& path, const std::string& fingerprint, - const scoped_refptr<ComponentInstaller>& installer, + const scoped_refptr<CrxInstaller>& installer, const scoped_refptr<OutOfProcessPatcher>& oop_patcher, const scoped_refptr<base::SequencedTaskRunner>& task_runner) : pk_hash_(pk_hash), @@ -270,7 +270,8 @@ void ComponentUnpacker::Finish() { base::DeleteFile(unpack_diff_path_, true); if (!unpack_path_.empty()) base::DeleteFile(unpack_path_, true); - callback_.Run(error_, extended_error_); + task_runner_->PostTask(FROM_HERE, + base::Bind(callback_, error_, extended_error_)); } ComponentUnpacker::~ComponentUnpacker() { diff --git a/components/update_client/component_unpacker.h b/components/update_client/component_unpacker.h index 033fd33..c1c6c9e 100644 --- a/components/update_client/component_unpacker.h +++ b/components/update_client/component_unpacker.h @@ -19,7 +19,7 @@ namespace update_client { -class ComponentInstaller; +class CrxInstaller; class ComponentPatcher; class OutOfProcessPatcher; @@ -99,7 +99,7 @@ class ComponentUnpacker : public base::RefCountedThreadSafe<ComponentUnpacker> { const std::vector<uint8_t>& pk_hash, const base::FilePath& path, const std::string& fingerprint, - const scoped_refptr<ComponentInstaller>& installer, + const scoped_refptr<CrxInstaller>& installer, const scoped_refptr<OutOfProcessPatcher>& oop_patcher, const scoped_refptr<base::SequencedTaskRunner>& task_runner); @@ -148,7 +148,7 @@ class ComponentUnpacker : public base::RefCountedThreadSafe<ComponentUnpacker> { bool is_delta_; std::string fingerprint_; scoped_refptr<ComponentPatcher> patcher_; - scoped_refptr<ComponentInstaller> installer_; + scoped_refptr<CrxInstaller> installer_; Callback callback_; scoped_refptr<OutOfProcessPatcher> oop_patcher_; Error error_; diff --git a/components/update_client/configurator.h b/components/update_client/configurator.h index b9a86fa..c4c763f 100644 --- a/components/update_client/configurator.h +++ b/components/update_client/configurator.h @@ -31,10 +31,8 @@ class OutOfProcessPatcher; // TODO(sorin): this class will be split soon in two. One class controls // the behavior of the update client, and the other class controls the // behavior of the component updater. -class Configurator { +class Configurator : public base::RefCountedThreadSafe<Configurator> { public: - virtual ~Configurator() {} - // Delay in seconds from calling Start() to the first update check. virtual int InitialDelay() const = 0; @@ -57,6 +55,10 @@ class Configurator { // for the same component. virtual int OnDemandDelay() const = 0; + // The time delay in seconds between applying updates for different + // components. + virtual int UpdateDelay() const = 0; + // The URLs for the update checks. The URLs are tried in order, the first one // that succeeds wins. virtual std::vector<GURL> UpdateUrl() const = 0; @@ -112,6 +114,11 @@ class Configurator { // initialized for use of COM objects. virtual scoped_refptr<base::SingleThreadTaskRunner> GetSingleThreadTaskRunner() const = 0; + + protected: + friend class base::RefCountedThreadSafe<Configurator>; + + virtual ~Configurator() {} }; } // namespace update_client diff --git a/components/update_client/crx_downloader.cc b/components/update_client/crx_downloader.cc index ecbf301..9bb7e18 100644 --- a/components/update_client/crx_downloader.cc +++ b/components/update_client/crx_downloader.cc @@ -29,22 +29,22 @@ CrxDownloader::DownloadMetrics::DownloadMetrics() // On Windows, the first downloader in the chain is a background downloader, // which uses the BITS service. -CrxDownloader* CrxDownloader::Create( +scoped_ptr<CrxDownloader> CrxDownloader::Create( bool is_background_download, net::URLRequestContextGetter* context_getter, - scoped_refptr<base::SequencedTaskRunner> url_fetcher_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> background_task_runner) { - scoped_ptr<CrxDownloader> url_fetcher_downloader( + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& background_task_runner) { + scoped_ptr<CrxDownloader> url_fetcher_downloader(scoped_ptr<CrxDownloader>( new UrlFetcherDownloader(scoped_ptr<CrxDownloader>().Pass(), - context_getter, url_fetcher_task_runner)); + context_getter, url_fetcher_task_runner))); #if defined(OS_WIN) if (is_background_download) { - return new BackgroundDownloader(url_fetcher_downloader.Pass(), - context_getter, background_task_runner); + return scoped_ptr<CrxDownloader>(new BackgroundDownloader( + url_fetcher_downloader.Pass(), context_getter, background_task_runner)); } #endif - return url_fetcher_downloader.release(); + return url_fetcher_downloader; } CrxDownloader::CrxDownloader(scoped_ptr<CrxDownloader> successor) diff --git a/components/update_client/crx_downloader.h b/components/update_client/crx_downloader.h index 661b5c8..7d7720f 100644 --- a/components/update_client/crx_downloader.h +++ b/components/update_client/crx_downloader.h @@ -79,13 +79,19 @@ class CrxDownloader { // download. The callback interface can be extended if needed to provide // more visibility into how the download has been handled, including // specific error codes and download metrics. - typedef base::Callback<void(const Result& result)> DownloadCallback; + using DownloadCallback = base::Callback<void(const Result& result)>; // The callback may fire 0 or many times during a download. Since this // class implements a chain of responsibility, the callback can fire for // different urls and different downloaders. The number of actual downloaded // bytes is not guaranteed to monotonically increment over time. - typedef base::Callback<void(const Result& result)> ProgressCallback; + using ProgressCallback = base::Callback<void(const Result& result)>; + + using Factory = scoped_ptr<CrxDownloader>(*)( + bool, + net::URLRequestContextGetter*, + const scoped_refptr<base::SequencedTaskRunner>&, + const scoped_refptr<base::SingleThreadTaskRunner>&); // Factory method to create an instance of this class and build the // chain of responsibility. |is_background_download| specifies that a @@ -93,11 +99,12 @@ class CrxDownloader { // |url_fetcher_task_runner| should be an IO capable task runner able to // support UrlFetcherDownloader. |background_task_runner| should be an // IO capable thread able to support BackgroundDownloader. - static CrxDownloader* Create( + static scoped_ptr<CrxDownloader> Create( bool is_background_download, net::URLRequestContextGetter* context_getter, - scoped_refptr<base::SequencedTaskRunner> url_fetcher_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> background_task_runner); + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner); virtual ~CrxDownloader(); void set_progress_callback(const ProgressCallback& progress_callback); diff --git a/components/update_client/crx_update_item.h b/components/update_client/crx_update_item.h index 09af62f..f066ca2 100644 --- a/components/update_client/crx_update_item.h +++ b/components/update_client/crx_update_item.h @@ -52,13 +52,17 @@ namespace update_client { // | error V | // +------------------------------------------ kUpdating ->----+ success // +// TODO(sorin): this data structure will be further refactored once +// the new update service is in place. For the time being, it remains as-is, +// since it is used by the old component update service. struct CrxUpdateItem { - enum Status { + enum class State { kNew, kChecking, kCanUpdate, kDownloadingDiff, kDownloading, + kDownloaded, kUpdatingDiff, kUpdating, kUpdated, @@ -69,7 +73,7 @@ struct CrxUpdateItem { // Call CrxUpdateService::ChangeItemState to change |status|. The function may // enforce conditions or notify observers of the change. - Status status; + State state; // True if the component was recently unregistered and will be uninstalled // soon (after the currently operation is finished, if there is one). diff --git a/components/update_client/ping_manager.cc b/components/update_client/ping_manager.cc index d3eb83c..24afde6 100644 --- a/components/update_client/ping_manager.cc +++ b/components/update_client/ping_manager.cc @@ -81,13 +81,13 @@ std::string BuildDownloadCompleteEventElements(const CrxUpdateItem* item) { // Returns a string representing one ping event xml element for an update item. std::string BuildUpdateCompleteEventElement(const CrxUpdateItem* item) { - DCHECK(item->status == CrxUpdateItem::kNoUpdate || - item->status == CrxUpdateItem::kUpdated); + DCHECK(item->state == CrxUpdateItem::State::kNoUpdate || + item->state == CrxUpdateItem::State::kUpdated); using base::StringAppendF; std::string ping_event("<event eventtype=\"3\""); - const int event_result = item->status == CrxUpdateItem::kUpdated; + const int event_result = item->state == CrxUpdateItem::State::kUpdated; StringAppendF(&ping_event, " eventresult=\"%d\"", event_result); if (item->error_category) StringAppendF(&ping_event, " errorcat=\"%d\"", item->error_category); diff --git a/components/update_client/ping_manager.h b/components/update_client/ping_manager.h index df2fa44..664142b 100644 --- a/components/update_client/ping_manager.h +++ b/components/update_client/ping_manager.h @@ -17,9 +17,9 @@ struct CrxUpdateItem; class PingManager { public: explicit PingManager(const Configurator& config); - ~PingManager(); + virtual ~PingManager(); - void OnUpdateComplete(const CrxUpdateItem* item); + virtual void OnUpdateComplete(const CrxUpdateItem* item); private: const Configurator& config_; diff --git a/components/update_client/task.h b/components/update_client/task.h new file mode 100644 index 0000000..51b6890 --- /dev/null +++ b/components/update_client/task.h @@ -0,0 +1,30 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_TASK_H_ +#define COMPONENTS_UPDATE_CLIENT_TASK_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "components/update_client/update_client.h" + +namespace update_client { + +class Task; + +// Defines an abstraction for a unit of work done by the update client. +// Each invocation of the update client API results in a task being created and +// run. In most cases, a task corresponds to a set of CRXs, which are updated +// together. +class Task { + public: + using Callback = base::Callback<void(Task* task, int error)>; + virtual ~Task() {} + + virtual void Run(const Callback& callback) = 0; +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_TASK_H_ diff --git a/components/update_client/task_update.cc b/components/update_client/task_update.cc new file mode 100644 index 0000000..2cbf001 --- /dev/null +++ b/components/update_client/task_update.cc @@ -0,0 +1,46 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "components/update_client/task_update.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/update_client/update_engine.h" + +namespace update_client { + +TaskUpdate::TaskUpdate(UpdateEngine* update_engine, + const std::vector<std::string>& ids, + const UpdateClient::CrxDataCallback& crx_data_callback) + : update_engine_(update_engine), + ids_(ids), + crx_data_callback_(crx_data_callback) { +} + +TaskUpdate::~TaskUpdate() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void TaskUpdate::Run(const Callback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + callback_ = callback; + + if (ids_.empty()) + RunComplete(-1); + + update_engine_->Update( + ids_, crx_data_callback_, + base::Bind(&TaskUpdate::RunComplete, base::Unretained(this))); +} + +void TaskUpdate::RunComplete(int error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + callback_.Run(this, error); +} + +} // namespace update_client diff --git a/components/update_client/task_update.h b/components/update_client/task_update.h new file mode 100644 index 0000000..7a91c28 --- /dev/null +++ b/components/update_client/task_update.h @@ -0,0 +1,52 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_ +#define COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_ + +#include <queue> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/task.h" +#include "components/update_client/update_client.h" + +namespace update_client { + +class UpdateEngine; + +// Defines a specialized task for updating a group of CRXs. +class TaskUpdate : public Task { + public: + TaskUpdate(UpdateEngine* update_engine, + const std::vector<std::string>& ids, + const UpdateClient::CrxDataCallback& crx_data_callback); + ~TaskUpdate() override; + + void Run(const Callback& callback) override; + + private: + // Called when the Run function associated with this task has completed. + void RunComplete(int error); + + base::ThreadChecker thread_checker_; + + UpdateEngine* update_engine_; // Not owned by this class. + + const std::vector<std::string> ids_; + const UpdateClient::CrxDataCallback crx_data_callback_; + + // Called to return the execution flow back to the caller of the + // Run function when this task has completed . + Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(TaskUpdate); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_ diff --git a/components/update_client/test/DEPS b/components/update_client/test/DEPS index 60dbcf4..d0c36c4 100644 --- a/components/update_client/test/DEPS +++ b/components/update_client/test/DEPS @@ -1,3 +1,5 @@ include_rules = [ "+content", + "+courgette", + "+net", ] diff --git a/components/update_client/test/crx_downloader_unittest.cc b/components/update_client/test/crx_downloader_unittest.cc index 5b01b42..4b80f13 100644 --- a/components/update_client/test/crx_downloader_unittest.cc +++ b/components/update_client/test/crx_downloader_unittest.cc @@ -113,10 +113,10 @@ void CrxDownloaderTest::SetUp() { num_progress_calls_ = 0; download_progress_result_ = CrxDownloader::Result(); - crx_downloader_.reset(CrxDownloader::Create( - false, // Do not use the background downloader in these tests. - context_.get(), base::MessageLoopProxy::current(), - NULL)); // No |background_task_runner| because no background downloader. + // Do not use the background downloader in these tests. + crx_downloader_.reset(CrxDownloader::Create(false, context_.get(), + base::MessageLoopProxy::current(), + NULL).release()); crx_downloader_->set_progress_callback(progress_callback_); get_interceptor_.reset(new GetInterceptor(base::MessageLoopProxy::current(), diff --git a/components/update_client/test/ping_manager_unittest.cc b/components/update_client/test/ping_manager_unittest.cc index 95539f03..23308ac 100644 --- a/components/update_client/test/ping_manager_unittest.cc +++ b/components/update_client/test/ping_manager_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" @@ -29,25 +30,25 @@ class ComponentUpdaterPingManagerTest : public testing::Test { void TearDown() override; protected: - scoped_ptr<TestConfigurator> config_; + scoped_refptr<TestConfigurator> config_; scoped_ptr<PingManager> ping_manager_; private: base::MessageLoopForIO loop_; }; -ComponentUpdaterPingManagerTest::ComponentUpdaterPingManagerTest() - : config_(new TestConfigurator(base::MessageLoopProxy::current(), - base::MessageLoopProxy::current())) { +ComponentUpdaterPingManagerTest::ComponentUpdaterPingManagerTest() { } void ComponentUpdaterPingManagerTest::SetUp() { + config_ = new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current()); ping_manager_.reset(new PingManager(*config_)); } void ComponentUpdaterPingManagerTest::TearDown() { ping_manager_.reset(); - config_.reset(); + config_ = nullptr; } void ComponentUpdaterPingManagerTest::RunThreadsUntilIdle() { @@ -65,7 +66,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test eventresult="1" is sent for successful updates. CrxUpdateItem item; item.id = "abc"; - item.status = CrxUpdateItem::kUpdated; + item.state = CrxUpdateItem::State::kUpdated; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); @@ -83,7 +84,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test eventresult="0" is sent for failed updates. item = CrxUpdateItem(); item.id = "abc"; - item.status = CrxUpdateItem::kNoUpdate; + item.state = CrxUpdateItem::State::kNoUpdate; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); @@ -101,7 +102,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test the error values and the fingerprints. item = CrxUpdateItem(); item.id = "abc"; - item.status = CrxUpdateItem::kNoUpdate; + item.state = CrxUpdateItem::State::kNoUpdate; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); item.previous_fp = "prev fp"; @@ -133,7 +134,7 @@ TEST_F(ComponentUpdaterPingManagerTest, DISABLED_PingManagerTest) { // Test the download metrics. item = CrxUpdateItem(); item.id = "abc"; - item.status = CrxUpdateItem::kUpdated; + item.state = CrxUpdateItem::State::kUpdated; item.previous_version = base::Version("1.0"); item.next_version = base::Version("2.0"); diff --git a/components/update_client/test/request_sender_unittest.cc b/components/update_client/test/request_sender_unittest.cc index f8ad042..35d4487 100644 --- a/components/update_client/test/request_sender_unittest.cc +++ b/components/update_client/test/request_sender_unittest.cc @@ -4,6 +4,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" @@ -40,7 +41,7 @@ class RequestSenderTest : public testing::Test { void RunThreads(); void RunThreadsUntilIdle(); - scoped_ptr<TestConfigurator> config_; + scoped_refptr<TestConfigurator> config_; scoped_ptr<RequestSender> request_sender_; scoped_ptr<InterceptorFactory> interceptor_factory_; @@ -66,8 +67,8 @@ RequestSenderTest::~RequestSenderTest() { } void RequestSenderTest::SetUp() { - config_.reset(new TestConfigurator(base::MessageLoopProxy::current(), - base::MessageLoopProxy::current())); + config_ = new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current()); interceptor_factory_.reset( new InterceptorFactory(base::MessageLoopProxy::current())); post_interceptor_1 = @@ -88,7 +89,7 @@ void RequestSenderTest::TearDown() { interceptor_factory_.reset(); - config_.reset(); + config_ = nullptr; RunThreadsUntilIdle(); } diff --git a/components/update_client/test/test_configurator.cc b/components/update_client/test/test_configurator.cc index 27aaf5f..23eaeff 100644 --- a/components/update_client/test/test_configurator.cc +++ b/components/update_client/test/test_configurator.cc @@ -68,6 +68,10 @@ int TestConfigurator::OnDemandDelay() const { return ondemand_time_; } +int TestConfigurator::UpdateDelay() const { + return 1; +} + std::vector<GURL> TestConfigurator::UpdateUrl() const { return MakeDefaultUrls(); } diff --git a/components/update_client/test/test_configurator.h b/components/update_client/test/test_configurator.h index 4943cca..6c2ed0e 100644 --- a/components/update_client/test/test_configurator.h +++ b/components/update_client/test/test_configurator.h @@ -55,7 +55,6 @@ class TestConfigurator : public Configurator { TestConfigurator( const scoped_refptr<base::SequencedTaskRunner>& worker_task_runner, const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner); - ~TestConfigurator() override; // Overrrides for Configurator. int InitialDelay() const override; @@ -64,6 +63,7 @@ class TestConfigurator : public Configurator { int StepDelayMedium() override; int MinimumReCheckWait() const override; int OnDemandDelay() const override; + int UpdateDelay() const override; std::vector<GURL> UpdateUrl() const override; std::vector<GURL> PingUrl() const override; base::Version GetBrowserVersion() const override; @@ -88,6 +88,10 @@ class TestConfigurator : public Configurator { void SetInitialDelay(int seconds); private: + friend class base::RefCountedThreadSafe<TestConfigurator>; + + ~TestConfigurator() override; + scoped_refptr<base::SequencedTaskRunner> worker_task_runner_; scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_; diff --git a/components/update_client/test/test_installer.h b/components/update_client/test/test_installer.h index a31c499..e15ae0a 100644 --- a/components/update_client/test/test_installer.h +++ b/components/update_client/test/test_installer.h @@ -17,9 +17,10 @@ class DictionaryValue; namespace update_client { +// TODO(sorin): consider reducing the number of the installer mocks. // A TestInstaller is an installer that does nothing for installation except // increment a counter. -class TestInstaller : public ComponentInstaller { +class TestInstaller : public CrxInstaller { public: TestInstaller(); diff --git a/components/update_client/test/update_checker_unittest.cc b/components/update_client/test/update_checker_unittest.cc index 20d3e79..ce6922a 100644 --- a/components/update_client/test/update_checker_unittest.cc +++ b/components/update_client/test/update_checker_unittest.cc @@ -60,7 +60,7 @@ class UpdateCheckerTest : public testing::Test { CrxUpdateItem BuildCrxUpdateItem(); - scoped_ptr<TestConfigurator> config_; + scoped_refptr<TestConfigurator> config_; scoped_ptr<UpdateChecker> update_checker_; @@ -86,8 +86,8 @@ UpdateCheckerTest::~UpdateCheckerTest() { } void UpdateCheckerTest::SetUp() { - config_.reset(new TestConfigurator(base::MessageLoopProxy::current(), - base::MessageLoopProxy::current())); + config_ = new TestConfigurator(base::MessageLoopProxy::current(), + base::MessageLoopProxy::current()); interceptor_factory_.reset( new InterceptorFactory(base::MessageLoopProxy::current())); post_interceptor_ = interceptor_factory_->CreateInterceptor(); @@ -106,7 +106,7 @@ void UpdateCheckerTest::TearDown() { post_interceptor_ = NULL; interceptor_factory_.reset(); - config_.reset(); + config_ = nullptr; // The PostInterceptor requires the message loop to run to destruct correctly. // TODO(sorin): This is fragile and should be fixed. @@ -155,7 +155,7 @@ CrxUpdateItem UpdateCheckerTest::BuildCrxUpdateItem() { crx_component.fingerprint = "fp1"; CrxUpdateItem crx_update_item; - crx_update_item.status = CrxUpdateItem::kNew; + crx_update_item.state = CrxUpdateItem::State::kNew; crx_update_item.id = "jebgalgnebhfojomionfpkfelancnnkf"; crx_update_item.component = crx_component; diff --git a/components/update_client/test/update_client_unittest.cc b/components/update_client/test/update_client_unittest.cc new file mode 100644 index 0000000..602fb96 --- /dev/null +++ b/components/update_client/test/update_client_unittest.cc @@ -0,0 +1,1422 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/thread_task_runner_handle.h" +#include "base/values.h" +#include "base/version.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/ping_manager.h" +#include "components/update_client/test/test_configurator.h" +#include "components/update_client/test/test_installer.h" +#include "components/update_client/update_checker.h" +#include "components/update_client/update_client_internal.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace update_client { + +namespace { + +using base::FilePath; + +// Makes a copy of the file specified by |from_path| in a temporary directory +// and returns the path of the copy. Returns true if successful. Cleans up if +// there was an error creating the copy. +bool MakeTestFile(const FilePath& from_path, FilePath* to_path) { + FilePath temp_dir; + bool result = + CreateNewTempDirectory(FILE_PATH_LITERAL("update_client"), &temp_dir); + if (!result) + return false; + + FilePath temp_file; + result = CreateTemporaryFileInDir(temp_dir, &temp_file); + if (!result) + return false; + + result = CopyFile(from_path, temp_file); + if (!result) { + DeleteFile(temp_file, false); + return false; + } + + *to_path = temp_file; + return true; +} + +using Events = UpdateClient::Observer::Events; + +class MockObserver : public UpdateClient::Observer { + public: + MOCK_METHOD2(OnEvent, void(Events event, const std::string&)); +}; + +class FakePingManagerImpl : public PingManager { + public: + explicit FakePingManagerImpl(const Configurator& config); + ~FakePingManagerImpl() override; + + void OnUpdateComplete(const CrxUpdateItem* item) override; + + const std::vector<CrxUpdateItem>& items() const; + + private: + std::vector<CrxUpdateItem> items_; + DISALLOW_COPY_AND_ASSIGN(FakePingManagerImpl); +}; + +FakePingManagerImpl::FakePingManagerImpl(const Configurator& config) + : PingManager(config) { +} + +FakePingManagerImpl::~FakePingManagerImpl() { +} + +void FakePingManagerImpl::OnUpdateComplete(const CrxUpdateItem* item) { + items_.push_back(*item); +} + +const std::vector<CrxUpdateItem>& FakePingManagerImpl::items() const { + return items_; +} + +} // namespace + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Mock; +using ::testing::Return; + +using content::BrowserThread; + +using std::string; + +class UpdateClientTest : public testing::Test { + public: + UpdateClientTest(); + ~UpdateClientTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + void RunThreads(); + + // Returns the full path to a test file. + static base::FilePath TestFilePath(const char* file); + + content::TestBrowserThreadBundle thread_bundle_; + + base::RunLoop runloop_; + base::Closure quit_closure_; + + scoped_refptr<update_client::TestConfigurator> config_; + + private: + DISALLOW_COPY_AND_ASSIGN(UpdateClientTest); +}; + +UpdateClientTest::UpdateClientTest() + : config_(new TestConfigurator( + BrowserThread::GetBlockingPool() + ->GetSequencedTaskRunnerWithShutdownBehavior( + BrowserThread::GetBlockingPool()->GetSequenceToken(), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))) { +} + +UpdateClientTest::~UpdateClientTest() { +} + +void UpdateClientTest::SetUp() { + quit_closure_ = runloop_.QuitClosure(); +} + +void UpdateClientTest::TearDown() { +} + +void UpdateClientTest::RunThreads() { + runloop_.Run(); +} + +base::FilePath UpdateClientTest::TestFilePath(const char* file) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + return path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("update_client") + .AppendASCII(file); +} + +// Tests the scenario where one update check is done for one CRX. The CRX +// has no update. +TEST_F(UpdateClientTest, OneCrxNoUpdate) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx; + crx.name = "test_jebg"; + crx.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx.version = Version("0.9"); + crx.installer = new TestInstaller; + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", + UpdateResponse::Results())); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { EXPECT_TRUE(items().empty()); } + }; + + scoped_ptr<PingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::Create)); + + MockObserver observer; + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure_)); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + +// Tests the scenario where two CRXs are checked for updates. On CRX has +// an update, the other CRX does not. +TEST_F(UpdateClientTest, TwoCrxUpdateNoUpdate) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx1; + crx1.name = "test_jebg"; + crx1.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx1.version = Version("0.9"); + crx1.installer = new TestInstaller; + + CrxComponent crx2; + crx2.name = "test_abag"; + crx2.pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash)); + crx2.version = Version("2.2"); + crx2.installer = new TestInstaller; + + components->push_back(crx1); + components->push_back(crx2); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + /* + Fake the following response: + + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='jebgalgnebhfojomionfpkfelancnnkf'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + </urls> + <manifest version='1.0' prodversionmin='11.0.1.0'> + <packages> + <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx"; + + UpdateResponse::Result result; + result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf"; + result.crx_urls.push_back(GURL("http://localhost/download/")); + result.manifest.version = "1.0"; + result.manifest.browser_min_version = "11.0.1.0"; + result.manifest.packages.push_back(package); + + UpdateResponse::Results results; + results.list.push_back(result); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { + DownloadMetrics download_metrics; + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 1843; + download_metrics.total_bytes = 1843; + download_metrics.download_time_ms = 1000; + + FilePath path; + EXPECT_TRUE(MakeTestFile( + TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); + + Result result; + result.error = 0; + result.response = path; + result.downloaded_bytes = 1843; + result.total_bytes = 1843; + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(1U, ping_items.size()); + EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_items[0].id); + EXPECT_TRUE(base::Version("0.9").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + } + }; + + scoped_ptr<PingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::Create)); + + MockObserver observer; + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + } + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "abagagagagagagagagagagagagagagag")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED, + "abagagagagagagagagagagagagagagag")).Times(1); + } + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + ids.push_back(std::string("abagagagagagagagagagagagagagagag")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure_)); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + +// Tests the update check for two CRXs scenario. Both CRXs have updates. +TEST_F(UpdateClientTest, TwoCrxUpdate) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx1; + crx1.name = "test_jebg"; + crx1.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx1.version = Version("0.9"); + crx1.installer = new TestInstaller; + + CrxComponent crx2; + crx2.name = "test_ihfo"; + crx2.pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); + crx2.version = Version("0.8"); + crx2.installer = new TestInstaller; + + components->push_back(crx1); + components->push_back(crx2); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + /* + Fake the following response: + + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='jebgalgnebhfojomionfpkfelancnnkf'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + </urls> + <manifest version='1.0' prodversionmin='11.0.1.0'> + <packages> + <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'/> + </packages> + </manifest> + </updatecheck> + </app> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + </urls> + <manifest version='1.0' prodversionmin='11.0.1.0'> + <packages> + <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package1; + package1.name = "jebgalgnebhfojomionfpkfelancnnkf.crx"; + + UpdateResponse::Result result1; + result1.extension_id = "jebgalgnebhfojomionfpkfelancnnkf"; + result1.crx_urls.push_back(GURL("http://localhost/download/")); + result1.manifest.version = "1.0"; + result1.manifest.browser_min_version = "11.0.1.0"; + result1.manifest.packages.push_back(package1); + + UpdateResponse::Result::Manifest::Package package2; + package2.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"; + + UpdateResponse::Result result2; + result2.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + result2.crx_urls.push_back(GURL("http://localhost/download/")); + result2.manifest.version = "1.0"; + result2.manifest.browser_min_version = "11.0.1.0"; + result2.manifest.packages.push_back(package2); + + UpdateResponse::Results results; + results.list.push_back(result1); + results.list.push_back(result2); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { + DownloadMetrics download_metrics; + FilePath path; + Result result; + if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") { + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 1843; + download_metrics.total_bytes = 1843; + download_metrics.download_time_ms = 1000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); + + result.error = 0; + result.response = path; + result.downloaded_bytes = 1843; + result.total_bytes = 1843; + } else if (url.path() == + "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") { + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 53638; + download_metrics.total_bytes = 53638; + download_metrics.download_time_ms = 2000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); + + result.error = 0; + result.response = path; + result.downloaded_bytes = 53638; + result.total_bytes = 53638; + } else { + NOTREACHED(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(2U, ping_items.size()); + EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_items[0].id); + EXPECT_TRUE(base::Version("0.9").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[1].id); + EXPECT_TRUE(base::Version("0.8").Equals(ping_items[1].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[1].next_version)); + EXPECT_EQ(0, ping_items[1].error_category); + EXPECT_EQ(0, ping_items[1].error_code); + } + }; + + scoped_ptr<FakePingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::Create)); + + MockObserver observer; + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + } + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_WAIT, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + } + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + ids.push_back(std::string("ihfokbkgjpifnbbojhneepfflplebdkc")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure_)); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + +// Tests the differential update scenario for one CRX. +TEST_F(UpdateClientTest, OneCrxDiffUpdate) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + static int num_calls = 0; + + // Must use the same stateful installer object. + static scoped_refptr<CrxInstaller> installer( + new VersionedTestInstaller()); + + ++num_calls; + + CrxComponent crx; + crx.name = "test_ihfo"; + crx.pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); + crx.installer = installer; + if (num_calls == 1) { + crx.version = Version("0.8"); + } else if (num_calls == 2) { + crx.version = Version("1.0"); + } else { + NOTREACHED(); + } + + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + static int num_call = 0; + ++num_call; + + UpdateResponse::Results results; + + if (num_call == 1) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + </urls> + <manifest version='1.0' prodversionmin='11.0.1.0'> + <packages> + <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"; + package.fingerprint = "1"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + result.crx_urls.push_back(GURL("http://localhost/download/")); + result.manifest.version = "1.0"; + result.manifest.browser_min_version = "11.0.1.0"; + result.manifest.packages.push_back(package); + results.list.push_back(result); + } else if (num_call == 2) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + <url codebasediff='http://localhost/download/'/> + </urls> + <manifest version='2.0' prodversionmin='11.0.1.0'> + <packages> + <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx' + namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx' + fp='22'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"; + package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"; + package.fingerprint = "22"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + result.crx_urls.push_back(GURL("http://localhost/download/")); + result.crx_diffurls.push_back(GURL("http://localhost/download/")); + result.manifest.version = "2.0"; + result.manifest.browser_min_version = "11.0.1.0"; + result.manifest.packages.push_back(package); + results.list.push_back(result); + } else { + NOTREACHED(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { + DownloadMetrics download_metrics; + FilePath path; + Result result; + if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") { + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 53638; + download_metrics.total_bytes = 53638; + download_metrics.download_time_ms = 2000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); + + result.error = 0; + result.response = path; + result.downloaded_bytes = 53638; + result.total_bytes = 53638; + } else if (url.path() == + "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") { + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 2105; + download_metrics.total_bytes = 2105; + download_metrics.download_time_ms = 1000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"), &path)); + + result.error = 0; + result.response = path; + result.downloaded_bytes = 2105; + result.total_bytes = 2105; + } else { + NOTREACHED(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(2U, ping_items.size()); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[0].id); + EXPECT_TRUE(base::Version("0.8").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[1].id); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[1].previous_version)); + EXPECT_TRUE(base::Version("2.0").Equals(ping_items[1].next_version)); + EXPECT_EQ(0, ping_items[1].diff_error_category); + EXPECT_EQ(0, ping_items[1].diff_error_code); + } + }; + + scoped_ptr<FakePingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::Create)); + + MockObserver observer; + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + } + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("ihfokbkgjpifnbbojhneepfflplebdkc")); + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + update_client->RemoveObserver(&observer); +} + +// Tests the update scenario for one CRX where the CRX installer returns +// an error. +TEST_F(UpdateClientTest, OneCrxInstallError) { + class MockInstaller : public CrxInstaller { + public: + MOCK_METHOD1(OnUpdateError, void(int error)); + MOCK_METHOD2(Install, + bool(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path)); + MOCK_METHOD2(GetInstalledFile, + bool(const std::string& file, base::FilePath* installed_file)); + MOCK_METHOD0(Uninstall, bool()); + + static void OnInstall(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + base::DeleteFile(unpack_path, true); + } + + protected: + ~MockInstaller() override {} + }; + + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + scoped_refptr<MockInstaller> installer(new MockInstaller()); + + EXPECT_CALL(*installer, OnUpdateError(_)).Times(0); + EXPECT_CALL(*installer, Install(_, _)) + .WillOnce(DoAll(Invoke(MockInstaller::OnInstall), Return(false))); + EXPECT_CALL(*installer, GetInstalledFile(_, _)).Times(0); + EXPECT_CALL(*installer, Uninstall()).Times(0); + + CrxComponent crx; + crx.name = "test_jebg"; + crx.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx.version = Version("0.9"); + crx.installer = installer; + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + /* + Fake the following response: + + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='jebgalgnebhfojomionfpkfelancnnkf'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + </urls> + <manifest version='1.0' prodversionmin='11.0.1.0'> + <packages> + <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx"; + + UpdateResponse::Result result; + result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf"; + result.crx_urls.push_back(GURL("http://localhost/download/")); + result.manifest.version = "1.0"; + result.manifest.browser_min_version = "11.0.1.0"; + result.manifest.packages.push_back(package); + + UpdateResponse::Results results; + results.list.push_back(result); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { + DownloadMetrics download_metrics; + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 1843; + download_metrics.total_bytes = 1843; + download_metrics.download_time_ms = 1000; + + FilePath path; + EXPECT_TRUE(MakeTestFile( + TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path)); + + Result result; + result.error = 0; + result.response = path; + result.downloaded_bytes = 1843; + result.total_bytes = 1843; + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(1U, ping_items.size()); + EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_items[0].id); + EXPECT_TRUE(base::Version("0.9").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(3, ping_items[0].error_category); // kInstallError. + EXPECT_EQ(9, ping_items[0].error_code); // kInstallerError. + } + }; + + scoped_ptr<PingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::Create)); + + MockObserver observer; + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED, + "jebgalgnebhfojomionfpkfelancnnkf")).Times(1); + } + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("jebgalgnebhfojomionfpkfelancnnkf")); + + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure_)); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + +// Tests the fallback from differential to full update scenario for one CRX. +TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + static int num_calls = 0; + + // Must use the same stateful installer object. + static scoped_refptr<CrxInstaller> installer( + new VersionedTestInstaller()); + + ++num_calls; + + CrxComponent crx; + crx.name = "test_ihfo"; + crx.pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash)); + crx.installer = installer; + if (num_calls == 1) { + crx.version = Version("0.8"); + } else if (num_calls == 2) { + crx.version = Version("1.0"); + } else { + NOTREACHED(); + } + + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, int error) { + EXPECT_EQ(0, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static scoped_ptr<UpdateChecker> Create(const Configurator& config) { + return scoped_ptr<UpdateChecker>(new FakeUpdateChecker()); + } + + bool CheckForUpdates( + const std::vector<CrxUpdateItem*>& items_to_check, + const std::string& additional_attributes, + const UpdateCheckCallback& update_check_callback) override { + static int num_call = 0; + ++num_call; + + UpdateResponse::Results results; + + if (num_call == 1) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + </urls> + <manifest version='1.0' prodversionmin='11.0.1.0'> + <packages> + <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"; + package.fingerprint = "1"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + result.crx_urls.push_back(GURL("http://localhost/download/")); + result.manifest.version = "1.0"; + result.manifest.browser_min_version = "11.0.1.0"; + result.manifest.packages.push_back(package); + results.list.push_back(result); + } else if (num_call == 2) { + /* + Fake the following response: + <?xml version='1.0' encoding='UTF-8'?> + <response protocol='3.0'> + <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> + <updatecheck status='ok'> + <urls> + <url codebase='http://localhost/download/'/> + <url codebasediff='http://localhost/download/'/> + </urls> + <manifest version='2.0' prodversionmin='11.0.1.0'> + <packages> + <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx' + namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx' + fp='22'/> + </packages> + </manifest> + </updatecheck> + </app> + </response> + */ + UpdateResponse::Result::Manifest::Package package; + package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"; + package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"; + package.fingerprint = "22"; + UpdateResponse::Result result; + result.extension_id = "ihfokbkgjpifnbbojhneepfflplebdkc"; + result.crx_urls.push_back(GURL("http://localhost/download/")); + result.crx_diffurls.push_back(GURL("http://localhost/download/")); + result.manifest.version = "2.0"; + result.manifest.browser_min_version = "11.0.1.0"; + result.manifest.packages.push_back(package); + results.list.push_back(result); + } else { + NOTREACHED(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static scoped_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& url_fetcher_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + background_task_runner) { + return scoped_ptr<CrxDownloader>(new FakeCrxDownloader()); + } + + private: + FakeCrxDownloader() : CrxDownloader(scoped_ptr<CrxDownloader>().Pass()) {} + ~FakeCrxDownloader() override {} + + void DoStartDownload(const GURL& url) override { + DownloadMetrics download_metrics; + FilePath path; + Result result; + if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") { + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 53638; + download_metrics.total_bytes = 53638; + download_metrics.download_time_ms = 2000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path)); + + result.error = 0; + result.response = path; + result.downloaded_bytes = 53638; + result.total_bytes = 53638; + } else if (url.path() == + "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") { + // A download error is injected on this execution path. + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = -1; + download_metrics.downloaded_bytes = 0; + download_metrics.total_bytes = 2105; + download_metrics.download_time_ms = 1000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"), &path)); + + result.error = -1; + result.response = path; + result.downloaded_bytes = 0; + result.total_bytes = 2105; + } else if (url.path() == + "/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx") { + download_metrics.url = url; + download_metrics.downloader = DownloadMetrics::kNone; + download_metrics.error = 0; + download_metrics.downloaded_bytes = 53855; + download_metrics.total_bytes = 53855; + download_metrics.download_time_ms = 1000; + + EXPECT_TRUE(MakeTestFile( + TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"), &path)); + + result.error = 0; + result.response = path; + result.downloaded_bytes = 53855; + result.total_bytes = 53855; + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FakeCrxDownloader::OnDownloadProgress, + base::Unretained(this), result)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeCrxDownloader::OnDownloadComplete, + base::Unretained(this), true, result, download_metrics)); + } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const Configurator& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { + const auto& ping_items = items(); + EXPECT_EQ(2U, ping_items.size()); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[0].id); + EXPECT_TRUE(base::Version("0.8").Equals(ping_items[0].previous_version)); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[0].next_version)); + EXPECT_EQ(0, ping_items[0].error_category); + EXPECT_EQ(0, ping_items[0].error_code); + EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_items[1].id); + EXPECT_TRUE(base::Version("1.0").Equals(ping_items[1].previous_version)); + EXPECT_TRUE(base::Version("2.0").Equals(ping_items[1].next_version)); + EXPECT_TRUE(ping_items[1].diff_update_failed); + EXPECT_EQ(1, ping_items[1].diff_error_category); // kNetworkError. + EXPECT_EQ(-1, ping_items[1].diff_error_code); + } + }; + + scoped_ptr<FakePingManager> ping_manager(new FakePingManager(*config_)); + scoped_ptr<UpdateClient> update_client(new UpdateClientImpl( + config_, ping_manager.Pass(), &FakeUpdateChecker::Create, + &FakeCrxDownloader::Create)); + + MockObserver observer; + { + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED, + "ihfokbkgjpifnbbojhneepfflplebdkc")).Times(1); + } + + update_client->AddObserver(&observer); + + std::vector<std::string> ids; + ids.push_back(std::string("ihfokbkgjpifnbbojhneepfflplebdkc")); + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + { + base::RunLoop runloop; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, runloop.QuitClosure())); + runloop.Run(); + } + + update_client->RemoveObserver(&observer); +} + +} // namespace update_client diff --git a/components/update_client/update_checker.h b/components/update_client/update_checker.h index 1ac6426..2f4b868 100644 --- a/components/update_client/update_checker.h +++ b/components/update_client/update_checker.h @@ -27,11 +27,13 @@ struct CrxUpdateItem; class UpdateChecker { public: - typedef base::Callback<void(const GURL& original_url, - int error, - const std::string& error_message, - const UpdateResponse::Results& results)> - UpdateCheckCallback; + using UpdateCheckCallback = + base::Callback<void(const GURL& original_url, + int error, + const std::string& error_message, + const UpdateResponse::Results& results)>; + + using Factory = scoped_ptr<UpdateChecker>(*)(const Configurator& config); virtual ~UpdateChecker() {} diff --git a/components/update_client/update_client.cc b/components/update_client/update_client.cc index 35cc5bb..ce857e1 100644 --- a/components/update_client/update_client.cc +++ b/components/update_client/update_client.cc @@ -4,12 +4,40 @@ #include "components/update_client/update_client.h" +#include <algorithm> +#include <queue> +#include <set> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/observer_list.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/configurator.h" #include "components/update_client/crx_update_item.h" +#include "components/update_client/ping_manager.h" +#include "components/update_client/task_update.h" +#include "components/update_client/update_checker.h" +#include "components/update_client/update_client_internal.h" +#include "components/update_client/update_engine.h" +#include "components/update_client/update_response.h" +#include "components/update_client/utils.h" +#include "url/gurl.h" namespace update_client { CrxUpdateItem::CrxUpdateItem() - : status(kNew), + : state(State::kNew), unregistered(false), on_demand(false), diff_update_failed(false), @@ -30,4 +58,122 @@ CrxComponent::CrxComponent() : allow_background_download(true) { CrxComponent::~CrxComponent() { } +UpdateClientImpl::UpdateClientImpl( + const scoped_refptr<Configurator>& config, + scoped_ptr<PingManager> ping_manager, + UpdateChecker::Factory update_checker_factory, + CrxDownloader::Factory crx_downloader_factory) + : config_(config), + ping_manager_(ping_manager.Pass()), + update_engine_( + new UpdateEngine(config, + update_checker_factory, + crx_downloader_factory, + ping_manager_.get(), + base::Bind(&UpdateClientImpl::NotifyObservers, + base::Unretained(this)))) { +} + +UpdateClientImpl::~UpdateClientImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); + config_ = nullptr; +} + +void UpdateClientImpl::Install(const std::string& id, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (update_engine_->IsUpdating(id)) { + completion_callback.Run(Error::ERROR_UPDATE_IN_PROGRESS); + return; + } + + std::vector<std::string> ids; + ids.push_back(id); + + scoped_ptr<TaskUpdate> task( + new TaskUpdate(update_engine_.get(), ids, crx_data_callback)); + + auto it = tasks_.insert(task.release()).first; + RunTask(*it, completion_callback); +} + +void UpdateClientImpl::Update(const std::vector<std::string>& ids, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_ptr<TaskUpdate> task( + new TaskUpdate(update_engine_.get(), ids, crx_data_callback)); + if (tasks_.empty()) { + auto it = tasks_.insert(task.release()).first; + RunTask(*it, completion_callback); + } else { + task_queue_.push(task.release()); + } +} + +void UpdateClientImpl::RunTask(Task* task, + const CompletionCallback& completion_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&Task::Run, base::Unretained(task), + base::Bind(&UpdateClientImpl::OnTaskComplete, + base::Unretained(this), completion_callback))); +} + +void UpdateClientImpl::OnTaskComplete( + const CompletionCallback& completion_callback, + Task* task, + int error) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(task); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(completion_callback, error)); + + tasks_.erase(task); + delete task; + + if (!task_queue_.empty()) { + RunTask(task_queue_.front(), completion_callback); + task_queue_.pop(); + } +} + +void UpdateClientImpl::AddObserver(Observer* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + observer_list_.AddObserver(observer); +} + +void UpdateClientImpl::RemoveObserver(Observer* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + observer_list_.RemoveObserver(observer); +} + +void UpdateClientImpl::NotifyObservers(Observer::Events event, + const std::string& id) { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(Observer, observer_list_, OnEvent(event, id)); +} + +bool UpdateClientImpl::GetCrxUpdateState(const std::string& id, + CrxUpdateItem* update_item) const { + return update_engine_->GetUpdateState(id, update_item); +} + +bool UpdateClientImpl::IsUpdating(const std::string& id) const { + return update_engine_->IsUpdating(id); +} + +scoped_ptr<UpdateClient> UpdateClientFactory( + const scoped_refptr<Configurator>& config) { + scoped_ptr<PingManager> ping_manager(new PingManager(*config)); + return scoped_ptr<UpdateClient>( + new UpdateClientImpl(config, ping_manager.Pass(), &UpdateChecker::Create, + &CrxDownloader::Create)); +} + } // namespace update_client diff --git a/components/update_client/update_client.h b/components/update_client/update_client.h index 35b3cca..100bbcc 100644 --- a/components/update_client/update_client.h +++ b/components/update_client/update_client.h @@ -9,9 +9,126 @@ #include <string> #include <vector> +#include "base/callback_forward.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/version.h" +// The UpdateClient class is a facade with a simple interface. The interface +// exposes a few APIs to install a CRX or update a group of CRXs. +// +// The difference between a CRX install and a CRX update is relatively minor. +// The terminology going forward will use the word "update" to cover both +// install and update scenarios, except where details regarding the install +// case are relevant. +// +// Handling an update consists of a series of actions such as sending an update +// check to the server, followed by parsing the server response, identifying +// the CRXs that require an update, downloading the differential update if +// it is available, unpacking and patching the differential update, then +// falling back to trying a similar set of actions using the full update. +// At the end of this process, completion pings are sent to the server, +// as needed, for the CRXs which had updates. +// +// As a general idea, this code handles the action steps needed to update +// a group of components serially, one step at a time. However, concurrent +// execution of calls to UpdateClient::Update is possible, therefore, +// queuing of updates could happen in some cases. More below. +// +// The UpdateClient class features a subject-observer interface to observe +// the CRX state changes during an update. +// +// The threading model for this code assumes that most of the code in the +// public interface runs on a SingleThreadTaskRunner. +// This task runner corresponds to the browser UI thread in many cases. There +// are parts of the installer interface that run on blocking task runners, which +// are usually threads in a thread pool. +// +// Using the UpdateClient is relatively easy. This assumes that the client +// of this code has already implemented the observer interface as needed, and +// can provide an installer, as described below. +// +// scoped_ptr<UpdateClient> update_client(UpdateClientFactory(...)); +// update_client->AddObserver(&observer); +// std::vector<std::string> ids; +// ids.push_back(...)); +// update_client->Update(ids, base::Bind(...), base::Bind(...)); +// +// UpdateClient::Update takes two callbacks as parameters. First callback +// allows the client of this code to provide an instance of CrxComponent +// data structure that specifies additional parameters of the update. +// CrxComponent has a CrxInstaller data member, which must be provided by the +// callers of this class. The second callback indicates that this non-blocking +// call has completed. +// +// There could be several ways of triggering updates for a CRX, user-initiated, +// or timer-based. Since the execution of updates is concurrent, the parameters +// for the update must be provided right before the update is handled. +// Otherwise, the version of the CRX set in the CrxComponent may not be correct. +// +// The UpdateClient public interface includes two functions: Install and +// Update. These functions correspond to installing one CRX immediately as a +// foreground activity (Install), and updating a group of CRXs silently in the +// background (Update). This distinction is important. Background updates are +// queued up and their actions run serially, one at a time, for the purpose of +// conserving local resources such as CPU, network, and I/O. +// On the other hand, installs are never queued up but run concurrently, as +// requested by the user. +// +// The update client introduces a runtime constraint regarding interleaving +// updates and installs. If installs or updates for a given CRX are in progress, +// then installs for the same CRX will fail with a specific error. +// +// Implementation details. +// +// The implementation details below are not relevant to callers of this +// code. However, these design notes are relevant to the owners and maintainers +// of this module. +// +// The design for the update client consists of a number of abstractions +// such as: task, update engine, update context, and action. +// The execution model for these abstractions is simple. They usually expose +// a public, non-blocking Run function, and they invoke a callback when +// the Run function has completed. +// +// A task is the unit of work for the UpdateClient. A task is associated +// with a single call of the Update function. A task represents a group +// of CRXs that are updated together. +// +// The UpdateClient is responsible for the queuing of tasks, if queuing is +// needed. +// +// When the task runs, it calls the update engine to handle the updates for +// the CRXs associated with the task. The UpdateEngine is the abstraction +// responsible for breaking down the update in a set of discrete steps, which +// are implemented as actions, and running the actions. +// +// The UpdateEngine maintains a set of UpdateContext instances. Each of +// these instances maintains the update state for all the CRXs belonging to +// a given task. The UpdateContext contains a queue of CRX ids. +// The UpdateEngine will handle updates for the CRXs in the order they appear +// in the queue, until the queue is empty. +// +// The update state for each CRX is maintained in a container of CrxUpdateItem*. +// As actions run, each action updates the CRX state, represented by one of +// these CrxUpdateItem* instances. +// +// Although the UpdateEngine can and will run update tasks concurrently, the +// actions of a task are run sequentially. +// +// The Action is a polymorphic type. There is some code reuse for convenience, +// implemented as a mixin. The polymorphic behavior of some of the actions +// is achieved using a template method. +// +// State changes of a CRX could generate events, which are observed using a +// subject-observer interface. +// +// The actions chain up. In some sense, the actions implement a state machine, +// as the CRX undergoes a series of state transitions in the process of +// being checked for updates and applying the update. + +class ComponentsUI; + namespace base { class DictionaryValue; class FilePath; @@ -19,63 +136,161 @@ class FilePath; namespace update_client { -// Component specific installers must derive from this class and implement -// OnUpdateError() and Install(). A valid instance of this class must be -// given to ComponentUpdateService::RegisterComponent(). -class ComponentInstaller - : public base::RefCountedThreadSafe<ComponentInstaller> { +class Configurator; +struct CrxUpdateItem; + +enum Error { + ERROR_UPDATE_IN_PROGRESS = 1, +}; + +// Defines an interface for a generic CRX installer. +class CrxInstaller : public base::RefCountedThreadSafe<CrxInstaller> { public: - // Called by the component updater on the main thread when there was a - // problem unpacking or verifying the component. |error| is a non-zero - // value which is only meaningful to the component updater. + // Called on the main thread when there was a problem unpacking or + // verifying the CRX. |error| is a non-zero value which is only meaningful + // to the caller. virtual void OnUpdateError(int error) = 0; - // Called by the component updater when a component has been unpacked - // and is ready to be installed. |manifest| contains the CRX manifest - // json dictionary and |unpack_path| contains the temporary directory - // with all the unpacked CRX files. This method may be called from - // a thread other than the main thread. + // Called by the update service when a CRX has been unpacked + // and it is ready to be installed. |manifest| contains the CRX manifest + // as a json dictionary.|unpack_path| contains the temporary directory + // with all the unpacked CRX files. + // This method may be called from a thread other than the main thread. virtual bool Install(const base::DictionaryValue& manifest, const base::FilePath& unpack_path) = 0; - // Set |installed_file| to the full path to the installed |file|. |file| is - // the filename of the file in this component's CRX. Returns false if this is + // Sets |installed_file| to the full path to the installed |file|. |file| is + // the filename of the file in this CRX. Returns false if this is // not possible (the file has been removed or modified, or its current - // location is unknown). Otherwise, returns true. + // location is unknown). Otherwise, it returns true. virtual bool GetInstalledFile(const std::string& file, base::FilePath* installed_file) = 0; - // Called by the component updater when a component has been unregistered and - // all versions should be uninstalled from disk. Returns true if - // uninstallation is supported, false otherwise. + // Called when a CRX has been unregistered and all versions should + // be uninstalled from disk. Returns true if uninstallation is supported, + // and false otherwise. virtual bool Uninstall() = 0; protected: - friend class base::RefCountedThreadSafe<ComponentInstaller>; + friend class base::RefCountedThreadSafe<CrxInstaller>; - virtual ~ComponentInstaller() {} + virtual ~CrxInstaller() {} }; -// Describes a particular component that can be installed or updated. This -// structure is required to register a component with the component updater. -// |pk_hash| is the SHA256 hash of the component's public key. If the component -// is to be installed then version should be "0" or "0.0", else it should be -// the current version. |fingerprint|, and |name| are optional. -// |allow_background_download| specifies that the component can be background -// downloaded in some cases. The default for this value is |true| and the value -// can be overriden at the registration time. This is a temporary change until -// the issue 340448 is resolved. +// TODO(sorin): this structure will be refactored soon. struct CrxComponent { + CrxComponent(); + ~CrxComponent(); + + // SHA256 hash of the CRX's public key. std::vector<uint8_t> pk_hash; - scoped_refptr<ComponentInstaller> installer; + scoped_refptr<CrxInstaller> installer; + + // The current version if the CRX is updated. Otherwise, "0" or "0.0" if + // the CRX is installed. Version version; - std::string fingerprint; - std::string name; + + std::string fingerprint; // Optional. + std::string name; // Optional. + + // Specifies that the CRX can be background-downloaded in some cases. + // The default for this value is |true| and the value can be overriden at + // the registration time. This is a temporary change until the issue + // crbug/340448 is resolved. bool allow_background_download; - CrxComponent(); - ~CrxComponent(); }; +// All methods are safe to call only from the browser's main thread. +class UpdateClient { + public: + using CrxDataCallback = + base::Callback<void(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components)>; + using CompletionCallback = base::Callback<void(int error)>; + + // Defines an interface to observe the UpdateClient. It provides + // notifications when state changes occur for the service itself or for the + // registered CRXs. + class Observer { + public: + enum class Events { + // Sent before the update client does an update check. + COMPONENT_CHECKING_FOR_UPDATES, + + // Sent when there is a new version of a registered CRX. After + // the notification is sent the CRX will be downloaded unless the + // update client inserts a + COMPONENT_UPDATE_FOUND, + + // Sent when a CRX is in the update queue but it can't be acted on + // right away, because the update client spaces out CRX updates due to a + // throttling policy. + COMPONENT_WAIT, + + // Sent after the new CRX has been downloaded but before the install + // or the upgrade is attempted. + COMPONENT_UPDATE_READY, + + // Sent when a CRX has been successfully updated. + COMPONENT_UPDATED, + + // Sent when a CRX has not been updated following an update check: + // either there was no update available, or the update failed. + COMPONENT_NOT_UPDATED, + + // Sent when CRX bytes are being downloaded. + COMPONENT_UPDATE_DOWNLOADING, + }; + + virtual ~Observer() {} + + // Called by the update client when a state change happens. + // If an |id| is specified, then the event is fired on behalf of the + // specific CRX. The implementors of this interface are + // expected to filter the relevant events based on the id of the CRX. + virtual void OnEvent(Events event, const std::string& id) = 0; + }; + + // Adds an observer for this class. An observer should not be added more + // than once. The caller retains the ownership of the observer object. + virtual void AddObserver(Observer* observer) = 0; + + // Removes an observer. It is safe for an observer to be removed while + // the observers are being notified. + virtual void RemoveObserver(Observer* observer) = 0; + + // Installs the specified CRX. Calls back after the install has been handled. + // Calls back on |completion_callback| after the update has been handled. The + // |error| parameter of the |completion_callback| contains an error code in + // the case of a run-time error, or 0 if the Install has been handled + // successfully. + virtual void Install(const std::string& id, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) = 0; + + // Updates the specified CRXs. Calls back on |crx_data_callback| before the + // update is attempted to give the caller the opportunity to provide the + // instances of CrxComponent to be used for this update. + virtual void Update(const std::vector<std::string>& ids, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) = 0; + + // Returns status details about a CRX update. The function returns true in + // case of success and false in case of errors, such as |id| was + // invalid or not known. + virtual bool GetCrxUpdateState(const std::string& id, + CrxUpdateItem* update_item) const = 0; + + virtual bool IsUpdating(const std::string& id) const = 0; + + virtual ~UpdateClient() {} +}; + +// Creates an instance of the update client. +// TODO(sorin): make UpdateClient a ref counted type. +scoped_ptr<UpdateClient> UpdateClientFactory( + const scoped_refptr<Configurator>& config); + } // namespace update_client #endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_ diff --git a/components/update_client/update_client_internal.h b/components/update_client/update_client_internal.h new file mode 100644 index 0000000..94caf04 --- /dev/null +++ b/components/update_client/update_client_internal.h @@ -0,0 +1,88 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_ +#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_ + +#include "components/update_client/update_client.h" + +#include <queue> +#include <set> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/crx_downloader.h" +#include "components/update_client/update_checker.h" + +namespace base { +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} // namespace base + +namespace update_client { + +class Configurator; +class PingManager; +class Task; +struct TaskContext; +class UpdateEngine; + +class UpdateClientImpl : public UpdateClient { + public: + UpdateClientImpl(const scoped_refptr<Configurator>& config, + scoped_ptr<PingManager> ping_manager, + UpdateChecker::Factory update_checker_factory, + CrxDownloader::Factory crx_downloader_factory); + ~UpdateClientImpl() override; + + // Overrides for UpdateClient. + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + void Install(const std::string& id, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) override; + void Update(const std::vector<std::string>& ids, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) override; + bool GetCrxUpdateState(const std::string& id, + CrxUpdateItem* update_item) const override; + bool IsUpdating(const std::string& id) const override; + + private: + void RunTask(Task* task, const CompletionCallback& completion_callback); + void OnTaskComplete(const CompletionCallback& completion_callback, + Task* task, + int error); + + void NotifyObservers(Observer::Events event, const std::string& id); + + base::ThreadChecker thread_checker_; + + scoped_refptr<Configurator> config_; + + // Contains the tasks that are queued up. + std::queue<Task*> task_queue_; + + // Contains all tasks in progress. + std::set<Task*> tasks_; + + // TODO(sorin): try to make the ping manager an observer of the service. + scoped_ptr<PingManager> ping_manager_; + scoped_ptr<UpdateEngine> update_engine_; + + ObserverList<Observer> observer_list_; + + // Used to post responses back to the main thread. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(UpdateClientImpl); +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_ diff --git a/components/update_client/update_engine.cc b/components/update_client/update_engine.cc new file mode 100644 index 0000000..4c64fde --- /dev/null +++ b/components/update_client/update_engine.cc @@ -0,0 +1,133 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/update_client/update_engine.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/thread_task_runner_handle.h" +#include "components/update_client/action_update_check.h" +#include "components/update_client/configurator.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/update_checker.h" + +namespace update_client { + +UpdateContext::UpdateContext( + const scoped_refptr<Configurator>& config, + const std::vector<std::string>& ids, + const UpdateClient::CrxDataCallback& crx_data_callback, + const UpdateEngine::NotifyObserversCallback& notify_observers_callback, + const UpdateEngine::CompletionCallback& callback, + UpdateChecker::Factory update_checker_factory, + CrxDownloader::Factory crx_downloader_factory, + PingManager* ping_manager) + : config(config), + ids(ids), + crx_data_callback(crx_data_callback), + notify_observers_callback(notify_observers_callback), + callback(callback), + main_task_runner(base::ThreadTaskRunnerHandle::Get()), + blocking_task_runner(config->GetSequencedTaskRunner()), + single_thread_task_runner(config->GetSingleThreadTaskRunner()), + update_checker_factory(update_checker_factory), + crx_downloader_factory(crx_downloader_factory), + ping_manager(ping_manager) { +} + +UpdateContext::~UpdateContext() { + STLDeleteElements(&update_items); +} + +UpdateEngine::UpdateEngine( + const scoped_refptr<Configurator>& config, + UpdateChecker::Factory update_checker_factory, + CrxDownloader::Factory crx_downloader_factory, + PingManager* ping_manager, + const NotifyObserversCallback& notify_observers_callback) + : config_(config), + update_checker_factory_(update_checker_factory), + crx_downloader_factory_(crx_downloader_factory), + ping_manager_(ping_manager), + notify_observers_callback_(notify_observers_callback) { +} + +UpdateEngine::~UpdateEngine() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +bool UpdateEngine::IsUpdating(const std::string& id) const { + DCHECK(thread_checker_.CalledOnValidThread()); + for (const auto& context : update_contexts_) { + const auto& ids = context->ids; + const auto it = std::find_if( + ids.begin(), ids.end(), + [id](const std::string& this_id) { return id == this_id; }); + if (it != ids.end()) { + return true; + } + } + return false; +} + +bool UpdateEngine::GetUpdateState(const std::string& id, + CrxUpdateItem* update_item) { + DCHECK(thread_checker_.CalledOnValidThread()); + for (const auto& context : update_contexts_) { + const auto& update_items = context->update_items; + const auto it = std::find_if(update_items.begin(), update_items.end(), + [id](const CrxUpdateItem* update_item) { + return id == update_item->id; + }); + if (it != update_items.end()) { + *update_item = **it; + return true; + } + } + return false; +} + +void UpdateEngine::Update( + const std::vector<std::string>& ids, + const UpdateClient::CrxDataCallback& crx_data_callback, + const CompletionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_ptr<UpdateContext> update_context(new UpdateContext( + config_, ids, crx_data_callback, notify_observers_callback_, callback, + update_checker_factory_, crx_downloader_factory_, ping_manager_)); + + CrxUpdateItem update_item; + scoped_ptr<ActionUpdateCheck> update_check_action(new ActionUpdateCheck( + (*update_context->update_checker_factory)(*config_).Pass(), + config_->GetBrowserVersion(), config_->ExtraRequestParams())); + + update_context->current_action.reset(update_check_action.release()); + update_contexts_.insert(update_context.get()); + + update_context->current_action->Run( + update_context.get(), + base::Bind(&UpdateEngine::UpdateComplete, base::Unretained(this), + update_context.get())); + + ignore_result(update_context.release()); +} + +void UpdateEngine::UpdateComplete(UpdateContext* update_context, int error) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(update_contexts_.find(update_context) != update_contexts_.end()); + + auto callback = update_context->callback; + + update_contexts_.erase(update_context); + delete update_context; + + callback.Run(error); +} + +} // namespace update_client diff --git a/components/update_client/update_engine.h b/components/update_client/update_engine.h new file mode 100644 index 0000000..e902a54 --- /dev/null +++ b/components/update_client/update_engine.h @@ -0,0 +1,149 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_ +#define COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_ + +#include <list> +#include <queue> +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "components/update_client/action.h" +#include "components/update_client/component_patcher_operation.h" +#include "components/update_client/component_unpacker.h" +#include "components/update_client/crx_downloader.h" +#include "components/update_client/crx_update_item.h" +#include "components/update_client/ping_manager.h" +#include "components/update_client/update_checker.h" +#include "components/update_client/update_client.h" + +namespace base { +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} // namespace base + +namespace update_client { + +class Configurator; +class CrxDownloader; +struct CrxUpdateItem; +class PingManager; +struct UpdateContext; + +// Handles updates for a group of components. Updates for different groups +// are run concurrently but within the same group of components, updates are +// applied one at a time. +class UpdateEngine { + public: + using CompletionCallback = base::Callback<void(int error)>; + using NotifyObserversCallback = + base::Callback<void(UpdateClient::Observer::Events event, + const std::string& id)>; + using CrxDataCallback = UpdateClient::CrxDataCallback; + + UpdateEngine(const scoped_refptr<Configurator>& config, + UpdateChecker::Factory update_checker_factory, + CrxDownloader::Factory crx_downloader_factory, + PingManager* ping_manager, + const NotifyObserversCallback& notify_observers_callback); + ~UpdateEngine(); + + // Returns true is the CRX identified by the given |id| is being updated. + bool IsUpdating(const std::string& id) const; + + bool GetUpdateState(const std::string& id, CrxUpdateItem* update_state); + + void Update(const std::vector<std::string>& ids, + const UpdateClient::CrxDataCallback& crx_data_callback, + const CompletionCallback& update_callback); + + private: + void UpdateComplete(UpdateContext* update_context, int error); + + base::ThreadChecker thread_checker_; + + scoped_refptr<Configurator> config_; + + UpdateChecker::Factory update_checker_factory_; + CrxDownloader::Factory crx_downloader_factory_; + + // TODO(sorin): refactor as a ref counted class. + PingManager* ping_manager_; // Not owned by this class. + + // Called when CRX state changes occur. + const NotifyObserversCallback notify_observers_callback_; + + // Contains the contexts associated with each update in progress. + std::set<UpdateContext*> update_contexts_; + + DISALLOW_COPY_AND_ASSIGN(UpdateEngine); +}; + +// TODO(sorin): consider making this a ref counted type. +struct UpdateContext { + UpdateContext( + const scoped_refptr<Configurator>& config, + const std::vector<std::string>& ids, + const UpdateClient::CrxDataCallback& crx_data_callback, + const UpdateEngine::NotifyObserversCallback& notify_observers_callback, + const UpdateEngine::CompletionCallback& callback, + UpdateChecker::Factory update_checker_factory, + CrxDownloader::Factory crx_downloader_factory, + PingManager* ping_manager); + + ~UpdateContext(); + + scoped_refptr<Configurator> config; + + // Contains the ids of all CRXs in this context. + const std::vector<std::string> ids; + + // Called before an update check, when update metadata is needed. + const UpdateEngine::CrxDataCallback& crx_data_callback; + + // Called when there is a state change for any update in this context. + const UpdateEngine::NotifyObserversCallback notify_observers_callback; + + // Called when the all updates associated with this context have completed. + const UpdateEngine::CompletionCallback callback; + + // Posts replies back to the main thread. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner; + + // Runs tasks in a blocking thread pool. + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner; + + // Runs tasks in the same single thread. + scoped_refptr<base::SingleThreadTaskRunner> single_thread_task_runner; + + // Creates instances of UpdateChecker; + UpdateChecker::Factory update_checker_factory; + + // Creates instances of CrxDownloader; + CrxDownloader::Factory crx_downloader_factory; + + PingManager* ping_manager; // Not owned by this class. + + scoped_ptr<Action> current_action; + + // TODO(sorin): use a map instead of vector. + std::vector<CrxUpdateItem*> update_items; + + // Contains the ids of the items to update. + std::queue<std::string> queue; + + scoped_ptr<CrxDownloader> crx_downloader; + scoped_refptr<ComponentUnpacker> unpacker; +}; + +} // namespace update_client + +#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_ |