| // 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 |