blob: 4bb2d3ade9e4449356101fb1f4b8d76cf30eb75f [file] [log] [blame]
// 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 <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/component.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/update_checker.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"
namespace update_client {
UpdateContext::UpdateContext(
const scoped_refptr<Configurator>& config,
bool is_foreground,
const std::vector<std::string>& ids,
UpdateClient::CrxDataCallback crx_data_callback,
const UpdateEngine::NotifyObserversCallback& notify_observers_callback,
UpdateEngine::Callback callback,
CrxDownloader::Factory crx_downloader_factory)
: config(config),
is_foreground(is_foreground),
enabled_component_updates(config->EnabledComponentUpdates()),
ids(ids),
crx_data_callback(std::move(crx_data_callback)),
notify_observers_callback(notify_observers_callback),
callback(std::move(callback)),
crx_downloader_factory(crx_downloader_factory) {
for (const auto& id : ids)
components.insert(
std::make_pair(id, base::MakeUnique<Component>(*this, id)));
}
UpdateContext::~UpdateContext() {}
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),
metadata_(
std::make_unique<PersistedData>(config->GetPrefService(),
config->GetActivityDataService())),
notify_observers_callback_(notify_observers_callback) {}
UpdateEngine::~UpdateEngine() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void UpdateEngine::Update(bool is_foreground,
const std::vector<std::string>& ids,
UpdateClient::CrxDataCallback crx_data_callback,
Callback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (IsThrottled(is_foreground)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), Error::RETRY_LATER));
return;
}
const auto result = update_contexts_.insert(base::MakeUnique<UpdateContext>(
config_, is_foreground, ids, std::move(crx_data_callback),
notify_observers_callback_, std::move(callback),
crx_downloader_factory_));
DCHECK(result.second);
const auto it = result.first;
const auto& update_context = *it;
DCHECK(update_context);
// Calls out to get the corresponding CrxComponent data for the CRXs in this
// update context.
DCHECK_EQ(ids.size(), update_context->ids.size());
DCHECK_EQ(update_context->ids.size(), update_context->components.size());
std::vector<CrxComponent> crx_components;
std::move(update_context->crx_data_callback)
.Run(update_context->ids, &crx_components);
DCHECK_EQ(update_context->ids.size(), crx_components.size());
for (size_t i = 0; i != update_context->ids.size(); ++i) {
const auto& id = update_context->ids[i];
const auto& crx_component = crx_components[i];
DCHECK_EQ(id, GetCrxComponentID(crx_component));
DCHECK_EQ(1u, update_context->components.count(id));
DCHECK(update_context->components.at(id));
auto& component = *update_context->components.at(id);
component.set_on_demand(update_context->is_foreground);
component.set_crx_component(crx_component);
component.set_previous_version(crx_component.version);
component.set_previous_fp(crx_component.fingerprint);
// Handle |kNew| state. This will transition the components to |kChecking|.
component.Handle(
base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesStart,
base::Unretained(this), it, id));
}
}
void UpdateEngine::ComponentCheckingForUpdatesStart(
const UpdateContextIterator it,
const std::string& id) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
DCHECK_EQ(1u, update_context->components.count(id));
DCHECK(update_context->components.at(id));
// Handle |kChecking| state.
auto& component = *update_context->components.at(id);
component.Handle(
base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesComplete,
base::Unretained(this), it));
++update_context->num_components_ready_to_check;
if (update_context->num_components_ready_to_check <
update_context->ids.size()) {
return;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&UpdateEngine::DoUpdateCheck, base::Unretained(this), it));
}
void UpdateEngine::DoUpdateCheck(const UpdateContextIterator it) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
update_context->update_checker =
update_checker_factory_(config_, metadata_.get());
update_context->update_checker->CheckForUpdates(
update_context->ids, update_context->components,
config_->ExtraRequestParams(), update_context->enabled_component_updates,
base::BindOnce(&UpdateEngine::UpdateCheckDone, base::Unretained(this),
it));
}
void UpdateEngine::UpdateCheckDone(const UpdateContextIterator it,
int error,
int retry_after_sec) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
update_context->retry_after_sec = retry_after_sec;
const int throttle_sec(update_context->retry_after_sec);
DCHECK_LE(throttle_sec, 24 * 60 * 60);
// Only positive values for throttle_sec are effective. 0 means that no
// throttling occurs and has the effect of resetting the member.
// Negative values are not trusted and are ignored.
if (throttle_sec >= 0) {
throttle_updates_until_ =
throttle_sec ? base::TimeTicks::Now() +
base::TimeDelta::FromSeconds(throttle_sec)
: base::TimeTicks();
}
update_context->update_check_error = error;
for (const auto& id : update_context->ids) {
DCHECK_EQ(1u, update_context->components.count(id));
DCHECK(update_context->components.at(id));
auto& component = *update_context->components.at(id);
component.UpdateCheckComplete();
}
}
void UpdateEngine::ComponentCheckingForUpdatesComplete(
const UpdateContextIterator it) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
++update_context->num_components_checked;
if (update_context->num_components_checked < update_context->ids.size()) {
return;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UpdateEngine::UpdateCheckComplete,
base::Unretained(this), it));
}
void UpdateEngine::UpdateCheckComplete(const UpdateContextIterator it) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
for (const auto& id : update_context->ids)
update_context->component_queue.push(id);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
base::Unretained(this), it));
}
void UpdateEngine::HandleComponent(const UpdateContextIterator it) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
auto& queue = update_context->component_queue;
if (queue.empty()) {
const Error error = update_context->update_check_error
? Error::UPDATE_CHECK_ERROR
: Error::NONE;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UpdateEngine::UpdateComplete,
base::Unretained(this), it, error));
return;
}
const auto& id = queue.front();
DCHECK_EQ(1u, update_context->components.count(id));
const auto& component = update_context->components.at(id);
DCHECK(component);
auto& next_update_delay = (*it)->next_update_delay;
if (!next_update_delay.is_zero() && component->IsUpdateAvailable()) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&UpdateEngine::HandleComponent, base::Unretained(this),
it),
next_update_delay);
next_update_delay = base::TimeDelta();
notify_observers_callback_.Run(
UpdateClient::Observer::Events::COMPONENT_WAIT, id);
return;
}
component->Handle(base::BindOnce(&UpdateEngine::HandleComponentComplete,
base::Unretained(this), it));
}
void UpdateEngine::HandleComponentComplete(const UpdateContextIterator it) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
auto& queue = update_context->component_queue;
DCHECK(!queue.empty());
const auto& id = queue.front();
DCHECK_EQ(1u, update_context->components.count(id));
const auto& component = update_context->components.at(id);
DCHECK(component);
if (component->IsHandled()) {
update_context->next_update_delay = component->GetUpdateDuration();
if (!component->events().empty()) {
ping_manager_->SendPing(*component);
}
queue.pop();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
base::Unretained(this), it));
}
void UpdateEngine::UpdateComplete(const UpdateContextIterator it, Error error) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto& update_context = *it;
DCHECK(update_context);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_context->callback), error));
update_contexts_.erase(it);
}
bool UpdateEngine::GetUpdateState(const std::string& id,
CrxUpdateItem* update_item) {
DCHECK(thread_checker_.CalledOnValidThread());
for (const auto& context : update_contexts_) {
const auto it = context->components.find(id);
if (it != context->components.end()) {
*update_item = it->second->GetCrxUpdateItem();
return true;
}
}
return false;
}
bool UpdateEngine::IsThrottled(bool is_foreground) const {
DCHECK(thread_checker_.CalledOnValidThread());
if (is_foreground || throttle_updates_until_.is_null())
return false;
const auto now(base::TimeTicks::Now());
// Throttle the calls in the interval (t - 1 day, t) to limit the effect of
// unset clocks or clock drift.
return throttle_updates_until_ - base::TimeDelta::FromDays(1) < now &&
now < throttle_updates_until_;
}
void UpdateEngine::SendUninstallPing(const std::string& id,
const base::Version& version,
int reason,
Callback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto result = update_contexts_.insert(base::MakeUnique<UpdateContext>(
config_, false, std::vector<std::string>{id},
UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
std::move(callback), nullptr));
DCHECK(result.second);
const auto it = result.first;
const auto& update_context = *it;
DCHECK(update_context);
DCHECK_EQ(1u, update_context->ids.size());
DCHECK_EQ(1u, update_context->components.count(id));
const auto& component = update_context->components.at(id);
component->Uninstall(version, reason);
update_context->component_queue.push(id);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
base::Unretained(this), it));
}
} // namespace update_client