diff options
Diffstat (limited to 'components/update_client')
39 files changed, 3558 insertions, 106 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..b43820b --- /dev/null +++ b/components/update_client/action_update.cc @@ -0,0 +1,370 @@ +// 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_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())); + + // Reset the unpacker last, otherwise we free our own arguments. + update_context->unpacker = nullptr; +} + +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..cabc4cb 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), 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..dbff673 --- /dev/null +++ b/components/update_client/test/update_client_unittest.cc @@ -0,0 +1,1417 @@ +// 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 path; + bool result = CreateTemporaryFile(&path); + if (!result) + return false; + + result = CopyFile(from_path, path); + if (!result) { + DeleteFile(path, false); + return result; + } + + *to_path = path; + 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() + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), + 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_ |