blob: b706ec4dc503d3f60254253b093890341348408b [file] [log] [blame]
// Copyright 2014 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/url_fetcher_downloader.h"
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#if defined(STARBOARD)
#include "cobalt/updater/updater_module.h"
#endif
#include "components/update_client/network.h"
#include "components/update_client/utils.h"
#include "url/gurl.h"
namespace {
#if defined(STARBOARD)
using cobalt::updater::UpdaterStatus;
using cobalt::updater::updater_status_string_map;
void CleanupDirectory(base::FilePath& dir) {
std::stack<std::string> directories;
base::FileEnumerator file_enumerator(
dir, true,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
for (auto path = file_enumerator.Next(); !path.value().empty();
path = file_enumerator.Next()) {
base::FileEnumerator::FileInfo info(file_enumerator.GetInfo());
if (info.IsDirectory()) {
directories.push(path.value());
} else {
SbFileDelete(path.value().c_str());
}
}
while (!directories.empty()) {
SbFileDelete(directories.top().c_str());
directories.pop();
}
}
#endif
const base::TaskTraits kTaskTraits = {
base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
} // namespace
namespace update_client {
#if defined(STARBOARD)
UrlFetcherDownloader::UrlFetcherDownloader(
std::unique_ptr<CrxDownloader> successor,
scoped_refptr<Configurator> config)
: CrxDownloader(std::move(successor)),
config_(config),
network_fetcher_factory_(config->GetNetworkFetcherFactory()) {
LOG(INFO) << "UrlFetcherDownloader::UrlFetcherDownloader";
}
#else
UrlFetcherDownloader::UrlFetcherDownloader(
std::unique_ptr<CrxDownloader> successor,
scoped_refptr<NetworkFetcherFactory> network_fetcher_factory)
: CrxDownloader(std::move(successor)),
network_fetcher_factory_(network_fetcher_factory) {}
#endif
UrlFetcherDownloader::~UrlFetcherDownloader() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(STARBOARD)
LOG(INFO) << "UrlFetcherDownloader::~UrlFetcherDownloader";
#endif
}
#if defined(STARBOARD)
#if defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::ConfirmSlot(const GURL& url, std::string* dst) {
#else // defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::ConfirmSlot(const GURL& url) {
#endif // defined(IN_MEMORY_UPDATES)
LOG(INFO) << "UrlFetcherDownloader::ConfirmSlot: url=" << url;
if (is_cancelled_) {
LOG(ERROR) << "UrlFetcherDownloader::ConfirmSlot: Download already cancelled";
ReportDownloadFailure(url);
return;
}
#if defined(IN_MEMORY_UPDATES)
if (!cobalt_slot_management_.ConfirmSlot(installation_dir_)) {
#else // defined(IN_MEMORY_UPDATES)
if (!cobalt_slot_management_.ConfirmSlot(download_dir_)) {
#endif // defined(IN_MEMORY_UPDATES)
ReportDownloadFailure(url, CrxDownloaderError::SLOT_UNAVAILABLE);
return;
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UrlFetcherDownloader::StartURLFetch,
#if defined(IN_MEMORY_UPDATES)
base::Unretained(this), url, dst));
#else // defined(IN_MEMORY_UPDATES)
base::Unretained(this), url));
#endif // defined(IN_MEMORY_UPDATES)
}
#if defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::SelectSlot(const GURL& url, std::string* dst) {
#else // defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::SelectSlot(const GURL& url) {
#endif // defined(IN_MEMORY_UPDATES)
LOG(INFO) << "UrlFetcherDownloader::SelectSlot: url=" << url;
if (is_cancelled_) {
LOG(ERROR) << "UrlFetcherDownloader::SelectSlot: Download already cancelled";
ReportDownloadFailure(url);
return;
}
#if defined(IN_MEMORY_UPDATES)
if (!cobalt_slot_management_.SelectSlot(&installation_dir_)) {
#else // defined(IN_MEMORY_UPDATES)
if (!cobalt_slot_management_.SelectSlot(&download_dir_)) {
#endif // defined(IN_MEMORY_UPDATES)
ReportDownloadFailure(url, CrxDownloaderError::SLOT_UNAVAILABLE);
return;
}
config_->SetUpdaterStatus(std::string(
updater_status_string_map.find(UpdaterStatus::kSlotLocked)->second));
// Use 15 sec delay to allow for other updaters/loaders to settle down.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&UrlFetcherDownloader::ConfirmSlot, base::Unretained(this),
#if defined(IN_MEMORY_UPDATES)
url, dst),
#else // defined(IN_MEMORY_UPDATES)
url),
#endif // defined(IN_MEMORY_UPDATES)
base::TimeDelta::FromSeconds(15));
}
#endif // defined(STARBOARD)
#if defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::DoStartDownload(const GURL& url, std::string* dst) {
#else // defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::DoStartDownload(const GURL& url) {
#endif // defined(IN_MEMORY_UPDATES)
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(STARBOARD)
LOG(INFO) << "UrlFetcherDownloader::DoStartDownload";
if (is_cancelled_) {
LOG(ERROR) << "UrlFetcherDownloader::DoStartDownload: Download already cancelled";
ReportDownloadFailure(url);
return;
}
const CobaltExtensionInstallationManagerApi* installation_api =
static_cast<const CobaltExtensionInstallationManagerApi*>(
SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
if (!installation_api) {
LOG(ERROR) << "Failed to get installation manager";
ReportDownloadFailure(url);
return;
}
if (!cobalt_slot_management_.Init(installation_api)) {
ReportDownloadFailure(url);
return;
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&UrlFetcherDownloader::SelectSlot,
#if defined(IN_MEMORY_UPDATES)
base::Unretained(this), url, dst));
#else // defined(IN_MEMORY_UPDATES)
base::Unretained(this), url));
#endif // defined(IN_MEMORY_UPDATES)
#else // defined(STARBOARD)
base::PostTaskWithTraitsAndReply(
FROM_HERE, kTaskTraits,
base::BindOnce(&UrlFetcherDownloader::CreateDownloadDir,
base::Unretained(this)),
base::BindOnce(&UrlFetcherDownloader::StartURLFetch,
base::Unretained(this), url));
#endif // defined(STARBOARD)
}
#if defined(STARBOARD)
void UrlFetcherDownloader::DoCancelDownload() {
LOG(INFO) << "UrlFetcherDownloader::DoCancelDownload";
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
is_cancelled_ = true;
if (network_fetcher_.get()) {
network_fetcher_->Cancel();
}
}
#endif
#if !defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::CreateDownloadDir() {
base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_url_fetcher_"),
&download_dir_);
}
#endif
#if defined(STARBOARD)
void UrlFetcherDownloader::ReportDownloadFailure(const GURL& url) {
LOG(INFO) << "UrlFetcherDownloader::ReportDownloadFailure";
ReportDownloadFailure(url, CrxDownloaderError::GENERIC_ERROR);
}
void UrlFetcherDownloader::ReportDownloadFailure(const GURL& url,
CrxDownloaderError error) {
#else
void UrlFetcherDownloader::ReportDownloadFailure(const GURL& url) {
#endif
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(STARBOARD)
cobalt_slot_management_.CleanupAllDrainFiles();
#endif
Result result;
#if defined(STARBOARD)
result.error = static_cast<int>(error);
#else
result.error = -1;
#endif
DownloadMetrics download_metrics;
download_metrics.url = url;
download_metrics.downloader = DownloadMetrics::kUrlFetcher;
download_metrics.error = -1;
download_metrics.downloaded_bytes = -1;
download_metrics.total_bytes = -1;
download_metrics.download_time_ms = 0;
main_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
base::Unretained(this), false, result, download_metrics));
}
#if defined(IN_MEMORY_UPDATES)
void UrlFetcherDownloader::StartURLFetch(const GURL& url, std::string* dst) {
#else
void UrlFetcherDownloader::StartURLFetch(const GURL& url) {
#endif
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(IN_MEMORY_UPDATES)
CHECK(dst != nullptr);
#endif
#if defined(STARBOARD)
LOG(INFO) << "UrlFetcherDownloader::StartURLFetch: url" << url
#if defined(IN_MEMORY_UPDATES)
<< " installation_dir=" << installation_dir_;
#else // defined(IN_MEMORY_UPDATES)
<< " download_dir=" << download_dir_;
#endif // defined(IN_MEMORY_UPDATES)
if (is_cancelled_) {
LOG(ERROR) << "UrlFetcherDownloader::StartURLFetch: Download already cancelled";
ReportDownloadFailure(url);
return;
}
#endif // defined(STARBOARD)
#if defined(IN_MEMORY_UPDATES)
if (installation_dir_.empty()) {
#else
if (download_dir_.empty()) {
#endif
#if defined(STARBOARD)
LOG(ERROR) << "UrlFetcherDownloader::StartURLFetch: failed with empty "
#if defined(IN_MEMORY_UPDATES)
<< "installation_dir";
#else // defined(IN_MEMORY_UPDATES)
<< "download_dir";
#endif // defined(IN_MEMORY_UPDATES)
#endif // defined(STARBOARD)
ReportDownloadFailure(url);
return;
}
network_fetcher_ = network_fetcher_factory_->Create();
#if defined(IN_MEMORY_UPDATES)
network_fetcher_->DownloadToString(
url,
dst,
base::BindOnce(&UrlFetcherDownloader::OnResponseStarted,
base::Unretained(this)),
base::BindRepeating(&UrlFetcherDownloader::OnDownloadProgress,
base::Unretained(this)),
base::BindOnce(&UrlFetcherDownloader::OnNetworkFetcherComplete,
base::Unretained(this)));
#else
const auto file_path = download_dir_.AppendASCII(url.ExtractFileName());
network_fetcher_->DownloadToFile(
url, file_path,
base::BindOnce(&UrlFetcherDownloader::OnResponseStarted,
base::Unretained(this)),
base::BindRepeating(&UrlFetcherDownloader::OnDownloadProgress,
base::Unretained(this)),
base::BindOnce(&UrlFetcherDownloader::OnNetworkFetcherComplete,
base::Unretained(this)));
#endif
download_start_time_ = base::TimeTicks::Now();
}
void UrlFetcherDownloader::OnNetworkFetcherComplete(
#if defined(IN_MEMORY_UPDATES)
std::string* dst,
#else
base::FilePath file_path,
#endif
int net_error,
int64_t content_size) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(STARBOARD)
LOG(INFO) << "UrlFetcherDownloader::OnNetworkFetcherComplete";
#endif
const base::TimeTicks download_end_time(base::TimeTicks::Now());
const base::TimeDelta download_time =
download_end_time >= download_start_time_
? download_end_time - download_start_time_
: base::TimeDelta();
// Consider a 5xx response from the server as an indication to terminate
// the request and avoid overloading the server in this case.
// is not accepting requests for the moment.
int error = -1;
#if defined(STARBOARD)
#if defined(IN_MEMORY_UPDATES)
if (response_code_ == 200 && net_error == 0) {
#else // defined(IN_MEMORY_UPDATES)
if (!file_path.empty() && response_code_ == 200 && net_error == 0) {
#endif // defined(IN_MEMORY_UPDATES)
#else // defined(STARBOARD)
if (!file_path.empty() && response_code_ == 200) {
#endif
DCHECK_EQ(0, net_error);
error = 0;
} else if (response_code_ != -1) {
error = response_code_;
} else {
error = net_error;
}
const bool is_handled = error == 0 || IsHttpServerError(error);
Result result;
result.error = error;
if (!error) {
#if defined(IN_MEMORY_UPDATES)
result.installation_dir = installation_dir_;
#else
result.response = file_path;
#endif
#if defined(STARBOARD)
result.installation_index = cobalt_slot_management_.GetInstallationIndex();
#endif
}
DownloadMetrics download_metrics;
download_metrics.url = url();
download_metrics.downloader = DownloadMetrics::kUrlFetcher;
download_metrics.error = error;
// Tests expected -1, in case of failures and no content is available.
download_metrics.downloaded_bytes = error ? -1 : content_size;
download_metrics.total_bytes = total_bytes_;
download_metrics.download_time_ms = download_time.InMilliseconds();
VLOG(1) << "Downloaded " << content_size << " bytes in "
<< download_time.InMilliseconds() << "ms from " << final_url_.spec()
#if defined(IN_MEMORY_UPDATES)
<< " to string";
#else
<< " to " << result.response.value();
#endif
#if !defined(IN_MEMORY_UPDATES)
#if !defined(STARBOARD)
// Delete the download directory in the error cases.
if (error && !download_dir_.empty())
base::PostTaskWithTraits(
FROM_HERE, kTaskTraits,
base::BindOnce(IgnoreResult(&base::DeleteFile), download_dir_, true));
#else // defined(STARBOARD)
if (error && !download_dir_.empty()) {
// Cleanup the download dir.
CleanupDirectory(download_dir_);
}
#endif // defined(STARBOARD)
#endif // !defined(IN_MEMORY_UPDATES)
main_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
base::Unretained(this), is_handled, result,
download_metrics));
}
// This callback is used to indicate that a download has been started.
void UrlFetcherDownloader::OnResponseStarted(const GURL& final_url,
int response_code,
int64_t content_length) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(STARBOARD)
LOG(INFO) << "UrlFetcherDownloader::OnResponseStarted";
#endif
VLOG(1) << "url fetcher response started for: " << final_url.spec();
final_url_ = final_url;
response_code_ = response_code;
total_bytes_ = content_length;
}
void UrlFetcherDownloader::OnDownloadProgress(int64_t current) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CrxDownloader::OnDownloadProgress();
}
} // namespace update_client