blob: 091f0e19bf80e90f69ada679d59eec0f2b0ee46d [file] [log] [blame]
// Copyright 2017 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/download/internal/background_service/in_memory_download.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/strings/string_util.h"
#include "components/download/internal/background_service/blob_task_proxy.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_fetcher.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
namespace download {
namespace {
// Converts a string to HTTP method used by URLFetcher.
net::URLFetcher::RequestType ToRequestType(const std::string& method) {
// Only supports GET and POST.
if (base::EqualsCaseInsensitiveASCII(method, "GET"))
return net::URLFetcher::RequestType::GET;
if (base::EqualsCaseInsensitiveASCII(method, "POST"))
return net::URLFetcher::RequestType::POST;
NOTREACHED();
return net::URLFetcher::RequestType::GET;
}
} // namespace
InMemoryDownloadImpl::ResponseWriter::ResponseWriter(
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
: paused_on_io_(false), io_task_runner_(io_task_runner) {}
InMemoryDownloadImpl::ResponseWriter::~ResponseWriter() = default;
void InMemoryDownloadImpl::ResponseWriter::Pause() {
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ResponseWriter::PauseOnIO, base::Unretained(this)));
}
void InMemoryDownloadImpl::ResponseWriter::PauseOnIO() {
io_task_runner_->BelongsToCurrentThread();
paused_on_io_ = true;
}
void InMemoryDownloadImpl::ResponseWriter::Resume() {
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ResponseWriter::ResumeOnIO, base::Unretained(this)));
}
void InMemoryDownloadImpl::ResponseWriter::ResumeOnIO() {
io_task_runner_->BelongsToCurrentThread();
paused_on_io_ = false;
// Continue read from network layer. Since we didn't write on pause, report
// 0 byte to network layer.
if (!write_callback_.is_null()) {
base::ResetAndReturn(&write_callback_).Run(0u);
}
}
std::unique_ptr<std::string> InMemoryDownloadImpl::ResponseWriter::TakeData() {
return std::move(data_);
}
int InMemoryDownloadImpl::ResponseWriter::Initialize(
const net::CompletionCallback& callback) {
data_ = std::make_unique<std::string>();
return net::OK;
}
int InMemoryDownloadImpl::ResponseWriter::Write(
net::IOBuffer* buffer,
int num_bytes,
const net::CompletionCallback& callback) {
io_task_runner_->BelongsToCurrentThread();
if (paused_on_io_) {
write_callback_ = callback;
return net::ERR_IO_PENDING;
}
DCHECK(data_);
data_->append(buffer->data(), num_bytes);
return num_bytes;
}
int InMemoryDownloadImpl::ResponseWriter::Finish(
int net_error,
const net::CompletionCallback& callback) {
io_task_runner_->BelongsToCurrentThread();
return net::OK;
}
InMemoryDownload::InMemoryDownload(const std::string& guid)
: guid_(guid), state_(State::INITIAL), bytes_downloaded_(0u) {}
InMemoryDownload::~InMemoryDownload() = default;
InMemoryDownloadImpl::InMemoryDownloadImpl(
const std::string& guid,
const RequestParams& request_params,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
Delegate* delegate,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
BlobTaskProxy::BlobContextGetter blob_context_getter,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
: InMemoryDownload(guid),
request_params_(request_params),
traffic_annotation_(traffic_annotation),
request_context_getter_(request_context_getter),
blob_task_proxy_(
BlobTaskProxy::Create(blob_context_getter, io_task_runner)),
io_task_runner_(io_task_runner),
delegate_(delegate),
paused_(false),
completion_notified_(false),
weak_ptr_factory_(this) {
DCHECK(!guid_.empty());
}
InMemoryDownloadImpl::~InMemoryDownloadImpl() {
io_task_runner_->DeleteSoon(FROM_HERE, blob_task_proxy_.release());
}
void InMemoryDownloadImpl::Start() {
DCHECK(state_ == State::INITIAL) << "Only call Start() for new download.";
SendRequest();
state_ = State::IN_PROGRESS;
}
void InMemoryDownloadImpl::Pause() {
if (paused_)
return;
paused_ = true;
switch (state_) {
case State::INITIAL:
// Do nothing.
return;
case State::IN_PROGRESS:
// Do nothing if network operation is done.
DCHECK(response_writer_);
response_writer_->Pause();
return;
case State::FAILED:
return;
case State::COMPLETE:
// Do nothing.
return;
}
}
void InMemoryDownloadImpl::Resume() {
if (!paused_)
return;
paused_ = false;
switch (state_) {
case State::INITIAL:
NOTREACHED();
return;
case State::IN_PROGRESS:
// Do nothing if network operation is done.
DCHECK(response_writer_);
response_writer_->Resume();
return;
case State::FAILED:
// Restart the network layer. No ongoing blob task should exist.
SendRequest();
state_ = State::IN_PROGRESS;
return;
case State::COMPLETE:
NotifyDelegateDownloadComplete();
return;
}
}
std::unique_ptr<storage::BlobDataHandle> InMemoryDownloadImpl::ResultAsBlob() {
DCHECK(state_ == State::COMPLETE || state_ == State::FAILED);
// Return a copy, we keep one reference of the underlying data to avoid
// unexpected deletion.
return std::make_unique<storage::BlobDataHandle>(*blob_data_handle_);
}
size_t InMemoryDownloadImpl::EstimateMemoryUsage() const {
return bytes_downloaded_;
}
void InMemoryDownloadImpl::OnURLFetchDownloadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total,
int64_t current_network_bytes) {
bytes_downloaded_ = current;
// TODO(xingliu): Throttle the update frequency. See https://crbug.com/809674.
if (delegate_)
delegate_->OnDownloadProgress(this);
}
void InMemoryDownloadImpl::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK(source);
response_headers_ = source->GetResponseHeaders();
switch (source->GetStatus().status()) {
case net::URLRequestStatus::Status::SUCCESS:
if (HandleResponseCode(source->GetResponseCode())) {
SaveAsBlob();
return;
}
state_ = State::FAILED;
NotifyDelegateDownloadComplete();
return;
case net::URLRequestStatus::Status::IO_PENDING:
return;
case net::URLRequestStatus::Status::CANCELED:
case net::URLRequestStatus::Status::FAILED:
state_ = State::FAILED;
NotifyDelegateDownloadComplete();
return;
}
}
bool InMemoryDownloadImpl::HandleResponseCode(int response_code) {
switch (response_code) {
case -1: // Non-HTTP request.
case net::HTTP_OK:
case net::HTTP_NON_AUTHORITATIVE_INFORMATION:
case net::HTTP_PARTIAL_CONTENT:
case net::HTTP_CREATED:
case net::HTTP_ACCEPTED:
case net::HTTP_NO_CONTENT:
case net::HTTP_RESET_CONTENT:
return true;
// All other codes are considered as failed.
default:
return false;
}
}
void InMemoryDownloadImpl::SaveAsBlob() {
DCHECK(url_fetcher_);
std::unique_ptr<std::string> data = response_writer_->TakeData();
auto callback = base::BindOnce(&InMemoryDownloadImpl::OnSaveBlobDone,
weak_ptr_factory_.GetWeakPtr());
blob_task_proxy_->SaveAsBlob(std::move(data), std::move(callback));
}
void InMemoryDownloadImpl::OnSaveBlobDone(
std::unique_ptr<storage::BlobDataHandle> blob_handle,
storage::BlobStatus status) {
// |status| is valid on IO thread, consumer of |blob_handle| should validate
// the data when using the blob data.
state_ =
(status == storage::BlobStatus::DONE) ? State::COMPLETE : State::FAILED;
// TODO(xingliu): Add metric for blob status code. If failed, consider remove
// |blob_data_handle_|. See https://crbug.com/809674.
blob_data_handle_ = std::move(blob_handle);
completion_time_ = base::Time::Now();
// Resets network backend.
response_writer_ = nullptr;
url_fetcher_.reset();
// Not considering |paused_| here, if pause after starting a blob operation,
// just let it finish.
NotifyDelegateDownloadComplete();
}
void InMemoryDownloadImpl::NotifyDelegateDownloadComplete() {
if (completion_notified_)
return;
completion_notified_ = true;
if (delegate_)
delegate_->OnDownloadComplete(this);
}
void InMemoryDownloadImpl::SendRequest() {
url_fetcher_ = net::URLFetcher::Create(request_params_.url,
ToRequestType(request_params_.method),
this, traffic_annotation_);
url_fetcher_->SetRequestContext(request_context_getter_.get());
url_fetcher_->SetExtraRequestHeaders(
request_params_.request_headers.ToString());
response_writer_ =
new ResponseWriter(request_context_getter_->GetNetworkTaskRunner());
url_fetcher_->SaveResponseWithWriter(
std::unique_ptr<ResponseWriter>(response_writer_));
url_fetcher_->Start();
// Pause on network thread if needed.
if (paused_)
response_writer_->Pause();
}
} // namespace download