blob: 15ab8286017e236caff6eada76d256195d6932ac [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 "components/download/internal/background_service/blob_task_proxy.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
namespace download {
InMemoryDownload::InMemoryDownload(const std::string& guid)
: guid_(guid),
state_(State::INITIAL),
paused_(false),
bytes_downloaded_(0u),
bytes_uploaded_(0u) {}
InMemoryDownload::~InMemoryDownload() = default;
InMemoryDownloadImpl::InMemoryDownloadImpl(
const std::string& guid,
const RequestParams& request_params,
scoped_refptr<network::ResourceRequestBody> request_body,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
Delegate* delegate,
network::mojom::URLLoaderFactory* url_loader_factory,
BlobTaskProxy::BlobContextGetter blob_context_getter,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
: InMemoryDownload(guid),
request_params_(request_params),
request_body_(std::move(request_body)),
traffic_annotation_(traffic_annotation),
url_loader_factory_(url_loader_factory),
blob_task_proxy_(
BlobTaskProxy::Create(blob_context_getter, io_task_runner)),
io_task_runner_(io_task_runner),
delegate_(delegate),
completion_notified_(false),
started_(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 (state_ == State::IN_PROGRESS)
paused_ = true;
}
void InMemoryDownloadImpl::Resume() {
paused_ = false;
switch (state_) {
case State::INITIAL:
NOTREACHED();
return;
case State::IN_PROGRESS:
// Let the network pipe continue to read data.
if (resume_callback_)
std::move(resume_callback_).Run();
return;
case State::FAILED:
// Restart the download.
Reset();
SendRequest();
state_ = State::IN_PROGRESS;
return;
case State::COMPLETE:
NotifyDelegateDownloadComplete();
return;
}
}
std::unique_ptr<storage::BlobDataHandle> InMemoryDownloadImpl::ResultAsBlob()
const {
DCHECK(state_ == State::COMPLETE || state_ == State::FAILED);
// Return a copy.
return std::make_unique<storage::BlobDataHandle>(*blob_data_handle_);
}
size_t InMemoryDownloadImpl::EstimateMemoryUsage() const {
return bytes_downloaded_;
}
void InMemoryDownloadImpl::OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) {
size_t size = string_piece.as_string().size();
data_.append(string_piece.as_string().data(), size);
bytes_downloaded_ += size;
if (paused_) {
// Read data later and cache the resumption callback when paused.
resume_callback_ = std::move(resume);
return;
}
// Continue to read data.
std::move(resume).Run();
// TODO(xingliu): Throttle the update frequency. See https://crbug.com/809674.
if (delegate_)
delegate_->OnDownloadProgress(this);
}
void InMemoryDownloadImpl::OnComplete(bool success) {
if (success) {
SaveAsBlob();
return;
}
state_ = State::FAILED;
// Release download data.
data_.clear();
// OnComplete() called without OnResponseStarted(). This will happen when the
// request was aborted.
if (!started_)
OnResponseStarted(GURL(), network::ResourceResponseHead());
NotifyDelegateDownloadComplete();
}
void InMemoryDownloadImpl::OnRetry(base::OnceClosure start_retry) {
Reset();
// The original URL is recorded in this class instead of |loader_|, so when
// running retry closure from SimpleUrlLoader, add back the original URL.
url_chain_.push_back(request_params_.url);
std::move(start_retry).Run();
}
void InMemoryDownloadImpl::SaveAsBlob() {
auto callback = base::BindOnce(&InMemoryDownloadImpl::OnSaveBlobDone,
weak_ptr_factory_.GetWeakPtr());
auto data = std::make_unique<std::string>(std::move(data_));
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.
DCHECK(data_.empty())
<< "Download data should be contained in |blob_data_handle_|.";
blob_data_handle_ = std::move(blob_handle);
completion_time_ = base::Time::Now();
// Resets network backend.
loader_.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() {
auto request = std::make_unique<network::ResourceRequest>();
request->url = request_params_.url;
request->method = request_params_.method;
request->headers = request_params_.request_headers;
request->load_flags = net::LOAD_DISABLE_CACHE;
if (request_body_) {
request->request_body = std::move(request_body_);
request->enable_upload_progress = true;
}
url_chain_.push_back(request_params_.url);
loader_ =
network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
loader_->SetOnRedirectCallback(base::BindRepeating(
&InMemoryDownloadImpl::OnRedirect, weak_ptr_factory_.GetWeakPtr()));
loader_->SetOnResponseStartedCallback(
base::BindRepeating(&InMemoryDownloadImpl::OnResponseStarted,
weak_ptr_factory_.GetWeakPtr()));
loader_->SetOnUploadProgressCallback(base::BindRepeating(
&InMemoryDownloadImpl::OnUploadProgress, weak_ptr_factory_.GetWeakPtr()));
// TODO(xingliu): Use SimpleURLLoader's retry when it won't hit CHECK in
// SharedURLLoaderFactory.
loader_->DownloadAsStream(url_loader_factory_, this);
}
void InMemoryDownloadImpl::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers) {
url_chain_.push_back(redirect_info.new_url);
}
void InMemoryDownloadImpl::OnResponseStarted(
const GURL& final_url,
const network::ResourceResponseHead& response_head) {
started_ = true;
response_headers_ = response_head.headers;
if (delegate_)
delegate_->OnDownloadStarted(this);
}
void InMemoryDownloadImpl::OnUploadProgress(uint64_t position, uint64_t total) {
bytes_uploaded_ = position;
if (delegate_)
delegate_->OnUploadProgress(this);
}
void InMemoryDownloadImpl::Reset() {
data_.clear();
url_chain_.clear();
response_headers_.reset();
bytes_downloaded_ = 0u;
completion_notified_ = false;
started_ = false;
resume_callback_.Reset();
}
} // namespace download