blob: 28a7d0f9bdfcb472340542e65716617bb56c0eed [file] [log] [blame] [edit]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/update_client/op_download.h"
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/update_client/cancellation.h"
#include "components/update_client/component.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_downloader.h"
#include "components/update_client/crx_downloader_factory.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/task_traits.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/update_client_metrics.h"
#include "components/update_client/update_engine.h"
#include "url/gurl.h"
namespace update_client {
namespace {
#if BUILDFLAG(IS_MAC)
// The minimum size of a download to attempt it at background priority.
constexpr int64_t kBackgroundDownloadSizeThreshold = 10'000'000; /*10 MB*/
#else
constexpr int64_t kBackgroundDownloadSizeThreshold = 0;
#endif
bool CanDoBackgroundDownload(bool is_foreground,
bool background_downloads_enabled,
int64_t size) {
return !is_foreground && background_downloads_enabled &&
size >= kBackgroundDownloadSizeThreshold;
}
// Returns a string literal corresponding to the value of the downloader |d|.
const char* DownloaderToString(CrxDownloader::DownloadMetrics::Downloader d) {
switch (d) {
case CrxDownloader::DownloadMetrics::kUrlFetcher:
return "direct";
case CrxDownloader::DownloadMetrics::kBits:
return "bits";
case CrxDownloader::DownloadMetrics::kBackgroundMac:
return "nsurlsession_background";
default:
return "unknown";
}
}
base::Value::Dict MakeEvent(const CrxDownloader::DownloadMetrics& dm) {
base::Value::Dict event;
event.Set("eventtype", protocol_request::kEventDownload);
event.Set("eventresult", static_cast<int>(dm.error == 0));
event.Set("downloader", DownloaderToString(dm.downloader));
if (dm.error) {
event.Set("errorcode", dm.error);
}
if (dm.extra_code1) {
event.Set("extracode1", dm.extra_code1);
}
event.Set("url", dm.url.spec());
// -1 means that the byte counts are not known.
if (dm.total_bytes >= 0 &&
dm.total_bytes < protocol_request::kProtocolMaxInt) {
event.Set("total", static_cast<double>(dm.total_bytes));
}
if (dm.downloaded_bytes >= 0 &&
dm.downloaded_bytes < protocol_request::kProtocolMaxInt) {
event.Set("downloaded", static_cast<double>(dm.downloaded_bytes));
}
if (dm.download_time_ms >= 0 &&
dm.download_time_ms < protocol_request::kProtocolMaxInt) {
event.Set("download_time_ms", static_cast<double>(dm.download_time_ms));
}
return event;
}
void DownloadComplete(
const std::string& id,
scoped_refptr<CrxDownloader> crx_downloader,
scoped_refptr<Cancellation> cancellation,
base::RepeatingCallback<void(base::Value::Dict)> event_adder,
base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
callback,
const CrxDownloader::Result& download_result) {
cancellation->Clear();
for (const auto& metric : crx_downloader->download_metrics()) {
event_adder.Run(MakeEvent(metric));
if (metric.error == 0) {
metrics::RecordCRXDownloadTime(
base::Milliseconds(metric.download_time_ms), id);
}
}
if (cancellation->IsCancelled()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
base::unexpected<CategorizedError>(
{.category = ErrorCategory::kService,
.code = static_cast<int>(ServiceError::CANCELLED)})));
return;
}
if (download_result.error) {
CHECK(download_result.response.empty());
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
base::unexpected<CategorizedError>(
{.category = ErrorCategory::kDownload,
.code = download_result.error,
.extra = download_result.extra_code1})));
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), download_result.response));
}
void HandleAvailableSpace(
scoped_refptr<Configurator> config,
const std::string& id,
scoped_refptr<Cancellation> cancellation,
bool is_foreground,
const std::vector<GURL>& urls,
int64_t size,
const std::string& hash,
CrxDownloader::ProgressCallback progress_callback,
base::RepeatingCallback<void(base::Value::Dict)> event_adder,
base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
callback,
int64_t available_bytes) {
if (available_bytes / 2 <= size) {
VLOG(1) << "available_bytes: " << available_bytes
<< ", download size: " << size;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
base::unexpected<CategorizedError>(
{.category = ErrorCategory::kDownload,
.code = static_cast<int>(CrxDownloaderError::DISK_FULL)})));
return;
}
scoped_refptr<CrxDownloader> crx_downloader =
config->GetCrxDownloaderFactory()->MakeCrxDownloader(
CanDoBackgroundDownload(is_foreground,
config->EnabledBackgroundDownloader(), size));
crx_downloader->set_progress_callback(progress_callback);
cancellation->OnCancel(crx_downloader->StartDownload(
urls, hash,
base::BindOnce(&DownloadComplete, id, crx_downloader, cancellation,
event_adder, std::move(callback))));
}
} // namespace
base::OnceClosure DownloadOperation(
scoped_refptr<Configurator> config,
const std::string& id,
base::RepeatingCallback<int64_t(const base::FilePath&)> get_available_space,
bool is_foreground,
const std::vector<GURL>& urls,
int64_t size,
const std::string& hash,
base::RepeatingCallback<void(base::Value::Dict)> event_adder,
base::RepeatingCallback<void(ComponentState)> state_tracker,
CrxDownloader::ProgressCallback progress_callback,
const base::FilePath& file,
base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
callback) {
state_tracker.Run(ComponentState::kDownloading);
auto cancellation = base::MakeRefCounted<Cancellation>();
progress_callback.Run(-1, -1);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(
[](base::RepeatingCallback<int64_t(const base::FilePath&)>
get_available_space) {
base::ScopedTempDir temp_dir;
return temp_dir.CreateUniqueTempDir()
? get_available_space.Run(temp_dir.GetPath())
: int64_t{0};
},
get_available_space),
base::BindOnce(&HandleAvailableSpace, config, id, cancellation,
is_foreground, urls, size, hash, progress_callback,
event_adder, std::move(callback)));
return base::BindOnce(&Cancellation::Cancel, cancellation);
}
} // namespace update_client