| // 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, |
| const UpdateClient::CrxDataCallback& crx_data_callback, |
| const UpdateEngine::NotifyObserversCallback& notify_observers_callback, |
| const UpdateEngine::Callback& callback, |
| CrxDownloader::Factory crx_downloader_factory) |
| : config(config), |
| is_foreground(is_foreground), |
| enabled_component_updates(config->EnabledComponentUpdates()), |
| ids(ids), |
| crx_data_callback(crx_data_callback), |
| notify_observers_callback(notify_observers_callback), |
| callback(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, |
| const UpdateClient::CrxDataCallback& crx_data_callback, |
| const Callback& callback) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (IsThrottled(is_foreground)) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(callback, Error::RETRY_LATER)); |
| return; |
| } |
| |
| const auto result = update_contexts_.insert(base::MakeUnique<UpdateContext>( |
| config_, is_foreground, ids, crx_data_callback, |
| notify_observers_callback_, 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; |
| 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::Bind(&UpdateEngine::ComponentCheckingForUpdatesStart, |
| base::Unretained(this), it, |
| base::ConstRef(component))); |
| } |
| } |
| |
| void UpdateEngine::ComponentCheckingForUpdatesStart( |
| const UpdateContextIterator& it, |
| const Component& component) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| const auto& update_context = *it; |
| DCHECK(update_context); |
| |
| const auto id = component.id(); |
| DCHECK_EQ(1u, update_context->components.count(id)); |
| DCHECK(update_context->components.at(id)); |
| |
| // Handle |kChecking| state. |
| auto& mutable_component = *update_context->components.at(id); |
| mutable_component.Handle(base::Bind( |
| &UpdateEngine::ComponentCheckingForUpdatesComplete, |
| base::Unretained(this), it, base::ConstRef(mutable_component))); |
| |
| ++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()); |
| |
| 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::Bind(&UpdateEngine::UpdateCheckDone, base::Unretained(this), it)); |
| } |
| |
| void UpdateEngine::UpdateCheckDone(const UpdateContextIterator& it, |
| int error, |
| int retry_after_sec) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| 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, |
| const Component& component) { |
| 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()); |
| |
| 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::Bind(&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::Bind(&UpdateEngine::HandleComponentComplete, |
| base::Unretained(this), it)); |
| } |
| |
| void UpdateEngine::HandleComponentComplete(const UpdateContextIterator& it) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| 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()) { |
| (*it)->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()); |
| |
| auto& update_context = *it; |
| DCHECK(update_context); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(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, |
| const 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(), |
| 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 |