blob: fa6c43611454fc713e18ced7dba380e01806a668 [file] [log] [blame]
// Copyright (c) 2011 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 "content/browser/download/download_item.h"
#include "base/basictypes.h"
#include "base/file_util.h"
#include "base/format_macros.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stringprintf.h"
#include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "net/base/net_util.h"
#include "content/browser/browser_thread.h"
#include "content/browser/content_browser_client.h"
#include "content/browser/download/download_file.h"
#include "content/browser/download/download_create_info.h"
#include "content/browser/download/download_file_manager.h"
#include "content/browser/download/download_manager.h"
#include "content/browser/download/download_manager_delegate.h"
#include "content/browser/download/download_persistent_store_info.h"
#include "content/browser/download/download_request_handle.h"
#include "content/browser/download/download_stats.h"
// A DownloadItem normally goes through the following states:
// * Created (when download starts)
// * Made visible to consumers (e.g. Javascript) after the
// destination file has been determined.
// * Entered into the history database.
// * Made visible in the download shelf.
// * All data is saved. Note that the actual data download occurs
// in parallel with the above steps, but until those steps are
// complete, completion of the data download will be ignored.
// * Download file is renamed to its final name, and possibly
// auto-opened.
// TODO(rdsmith): This progress should be reflected in
// DownloadItem::DownloadState and a state transition table/state diagram.
//
// TODO(rdsmith): This description should be updated to reflect the cancel
// pathways.
namespace {
// Update frequency (milliseconds).
const int kUpdateTimeMs = 1000;
static void DeleteDownloadedFile(const FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// Make sure we only delete files.
if (!file_util::DirectoryExists(path))
file_util::Delete(path, false);
}
const char* DebugSafetyStateString(DownloadItem::SafetyState state) {
switch (state) {
case DownloadItem::SAFE:
return "SAFE";
case DownloadItem::DANGEROUS:
return "DANGEROUS";
case DownloadItem::DANGEROUS_BUT_VALIDATED:
return "DANGEROUS_BUT_VALIDATED";
default:
NOTREACHED() << "Unknown safety state " << state;
return "unknown";
};
}
const char* DebugDownloadStateString(DownloadItem::DownloadState state) {
switch (state) {
case DownloadItem::IN_PROGRESS:
return "IN_PROGRESS";
case DownloadItem::COMPLETE:
return "COMPLETE";
case DownloadItem::CANCELLED:
return "CANCELLED";
case DownloadItem::REMOVING:
return "REMOVING";
case DownloadItem::INTERRUPTED:
return "INTERRUPTED";
default:
NOTREACHED() << "Unknown download state " << state;
return "unknown";
};
}
DownloadItem::SafetyState GetSafetyState(bool dangerous_file,
bool dangerous_url) {
return (dangerous_url || dangerous_file) ?
DownloadItem::DANGEROUS : DownloadItem::SAFE;
}
// Note: When a download has both |dangerous_file| and |dangerous_url| set,
// danger type is set to DANGEROUS_URL since the risk of dangerous URL
// overweights that of dangerous file type.
DownloadItem::DangerType GetDangerType(bool dangerous_file,
bool dangerous_url) {
if (dangerous_url) {
// dangerous URL overweights dangerous file. We check dangerous URL first.
return DownloadItem::DANGEROUS_URL;
}
return dangerous_file ?
DownloadItem::DANGEROUS_FILE : DownloadItem::NOT_DANGEROUS;
}
} // namespace
// Our download table ID starts at 1, so we use 0 to represent a download that
// has started, but has not yet had its data persisted in the table. We use fake
// database handles in incognito mode starting at -1 and progressively getting
// more negative.
// static
const int DownloadItem::kUninitializedHandle = 0;
// Constructor for reading from the history service.
DownloadItem::DownloadItem(DownloadManager* download_manager,
const DownloadPersistentStoreInfo& info)
: download_id_(-1),
full_path_(info.path),
url_chain_(1, info.url),
referrer_url_(info.referrer_url),
total_bytes_(info.total_bytes),
received_bytes_(info.received_bytes),
start_tick_(base::TimeTicks()),
state_(static_cast<DownloadState>(info.state)),
start_time_(info.start_time),
db_handle_(info.db_handle),
download_manager_(download_manager),
is_paused_(false),
open_when_complete_(false),
file_externally_removed_(false),
safety_state_(SAFE),
auto_opened_(false),
is_otr_(false),
is_temporary_(false),
all_data_saved_(false),
opened_(false),
open_enabled_(true),
delegate_delayed_complete_(false) {
if (IsInProgress())
state_ = CANCELLED;
if (IsComplete())
all_data_saved_ = true;
Init(false /* not actively downloading */);
}
// Constructing for a regular download:
DownloadItem::DownloadItem(DownloadManager* download_manager,
const DownloadCreateInfo& info,
bool is_otr)
: state_info_(info.original_name, info.save_info.file_path,
info.has_user_gesture, info.transition_type,
info.prompt_user_for_save_location, info.path_uniquifier,
false, false),
request_handle_(info.request_handle),
download_id_(info.download_id),
full_path_(info.path),
url_chain_(info.url_chain),
referrer_url_(info.referrer_url),
suggested_filename_(UTF16ToUTF8(info.save_info.suggested_name)),
content_disposition_(info.content_disposition),
mime_type_(info.mime_type),
original_mime_type_(info.original_mime_type),
referrer_charset_(info.referrer_charset),
total_bytes_(info.total_bytes),
received_bytes_(0),
last_os_error_(0),
start_tick_(base::TimeTicks::Now()),
state_(IN_PROGRESS),
start_time_(info.start_time),
db_handle_(DownloadItem::kUninitializedHandle),
download_manager_(download_manager),
is_paused_(false),
open_when_complete_(false),
file_externally_removed_(false),
safety_state_(SAFE),
auto_opened_(false),
is_otr_(is_otr),
is_temporary_(!info.save_info.file_path.empty()),
all_data_saved_(false),
opened_(false),
open_enabled_(true) {
Init(true /* actively downloading */);
}
// Constructing for the "Save Page As..." feature:
DownloadItem::DownloadItem(DownloadManager* download_manager,
const FilePath& path,
const GURL& url,
bool is_otr,
int download_id)
: download_id_(download_id),
full_path_(path),
url_chain_(1, url),
referrer_url_(GURL()),
total_bytes_(0),
received_bytes_(0),
last_os_error_(0),
start_tick_(base::TimeTicks::Now()),
state_(IN_PROGRESS),
start_time_(base::Time::Now()),
db_handle_(DownloadItem::kUninitializedHandle),
download_manager_(download_manager),
is_paused_(false),
open_when_complete_(false),
file_externally_removed_(false),
safety_state_(SAFE),
auto_opened_(false),
is_otr_(is_otr),
is_temporary_(false),
all_data_saved_(false),
opened_(false),
open_enabled_(true) {
Init(true /* actively downloading */);
}
DownloadItem::~DownloadItem() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TransitionTo(REMOVING);
download_manager_->AssertQueueStateConsistent(this);
}
void DownloadItem::AddObserver(Observer* observer) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
observers_.AddObserver(observer);
}
void DownloadItem::RemoveObserver(Observer* observer) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
observers_.RemoveObserver(observer);
}
void DownloadItem::UpdateObservers() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this));
}
bool DownloadItem::CanShowInFolder() {
return !IsCancelled() && !file_externally_removed_;
}
bool DownloadItem::CanOpenDownload() {
return !file_externally_removed_;
}
bool DownloadItem::ShouldOpenFileBasedOnExtension() {
return download_manager_->delegate()->ShouldOpenFileBasedOnExtension(
GetUserVerifiedFilePath());
}
void DownloadItem::OpenDownload() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (IsPartialDownload()) {
open_when_complete_ = !open_when_complete_;
return;
}
if (!IsComplete() || file_externally_removed_)
return;
// Ideally, we want to detect errors in opening and report them, but we
// don't generally have the proper interface for that to the external
// program that opens the file. So instead we spawn a check to update
// the UI if the file has been deleted in parallel with the open.
download_manager_->CheckForFileRemoval(this);
opened_ = true;
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this));
// For testing: If download opening is disabled on this item,
// make the rest of the routine a no-op.
if (!open_enabled_)
return;
if (download_manager_->delegate()->ShouldOpenDownload(this))
content::GetContentClient()->browser()->OpenItem(full_path());
}
void DownloadItem::ShowDownloadInShell() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::GetContentClient()->browser()->ShowItemInFolder(full_path());
}
void DownloadItem::DangerousDownloadValidated() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK_EQ(DANGEROUS, safety_state());
UMA_HISTOGRAM_ENUMERATION("Download.DangerousDownloadValidated",
GetDangerType(),
DANGEROUS_TYPE_MAX);
safety_state_ = DANGEROUS_BUT_VALIDATED;
UpdateObservers();
download_manager_->MaybeCompleteDownload(this);
}
void DownloadItem::UpdateSize(int64 bytes_so_far) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
received_bytes_ = bytes_so_far;
// If we've received more data than we were expecting (bad server info?),
// revert to 'unknown size mode'.
if (received_bytes_ > total_bytes_)
total_bytes_ = 0;
}
void DownloadItem::StartProgressTimer() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
update_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kUpdateTimeMs), this,
&DownloadItem::UpdateObservers);
}
void DownloadItem::StopProgressTimer() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
update_timer_.Stop();
}
// Updates from the download thread may have been posted while this download
// was being cancelled in the UI thread, so we'll accept them unless we're
// complete.
void DownloadItem::Update(int64 bytes_so_far) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!IsInProgress()) {
NOTREACHED();
return;
}
UpdateSize(bytes_so_far);
UpdateObservers();
}
// Triggered by a user action.
void DownloadItem::Cancel(bool update_history) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
if (!IsPartialDownload()) {
// Small downloads might be complete before this method has
// a chance to run.
return;
}
download_stats::RecordDownloadCount(download_stats::CANCELLED_COUNT);
TransitionTo(CANCELLED);
StopProgressTimer();
if (update_history)
download_manager_->DownloadCancelledInternal(this);
}
void DownloadItem::MarkAsComplete() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(all_data_saved_);
TransitionTo(COMPLETE);
}
void DownloadItem::CompleteDelayedDownload() {
auto_opened_ = true;
Completed();
}
void DownloadItem::OnAllDataSaved(int64 size) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!all_data_saved_);
all_data_saved_ = true;
UpdateSize(size);
StopProgressTimer();
}
void DownloadItem::OnDownloadedFileRemoved() {
file_externally_removed_ = true;
UpdateObservers();
}
void DownloadItem::Completed() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(20) << __FUNCTION__ << "() " << DebugString(false);
DCHECK(all_data_saved_);
TransitionTo(COMPLETE);
download_manager_->DownloadCompleted(id());
download_stats::RecordDownloadCompleted(start_tick_);
if (auto_opened_) {
// If it was already handled by the delegate, do nothing.
} else if (open_when_complete() ||
ShouldOpenFileBasedOnExtension() ||
is_temporary()) {
// If the download is temporary, like in drag-and-drop, do not open it but
// we still need to set it auto-opened so that it can be removed from the
// download shelf.
if (!is_temporary())
OpenDownload();
auto_opened_ = true;
UpdateObservers();
}
}
void DownloadItem::TransitionTo(DownloadState new_state) {
if (state_ == new_state)
return;
state_ = new_state;
UpdateObservers();
}
void DownloadItem::UpdateSafetyState() {
SafetyState updated_value(
GetSafetyState(state_info_.is_dangerous_file,
state_info_.is_dangerous_url));
if (updated_value != safety_state_) {
safety_state_ = updated_value;
UpdateObservers();
}
}
void DownloadItem::UpdateTarget() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (state_info_.target_name.value().empty())
state_info_.target_name = full_path_.BaseName();
}
void DownloadItem::Interrupted(int64 size, int os_error) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!IsInProgress())
return;
last_os_error_ = os_error;
UpdateSize(size);
StopProgressTimer();
download_stats::RecordDownloadInterrupted(os_error,
received_bytes_,
total_bytes_);
TransitionTo(INTERRUPTED);
}
void DownloadItem::Delete(DeleteReason reason) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
switch (reason) {
case DELETE_DUE_TO_USER_DISCARD:
UMA_HISTOGRAM_ENUMERATION("Download.UserDiscard", GetDangerType(),
DANGEROUS_TYPE_MAX);
break;
case DELETE_DUE_TO_BROWSER_SHUTDOWN:
UMA_HISTOGRAM_ENUMERATION("Download.Discard", GetDangerType(),
DANGEROUS_TYPE_MAX);
break;
default:
NOTREACHED();
}
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
NewRunnableFunction(&DeleteDownloadedFile, full_path_));
Remove();
// We have now been deleted.
}
void DownloadItem::Remove() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
download_manager_->AssertQueueStateConsistent(this);
Cancel(true);
download_manager_->AssertQueueStateConsistent(this);
TransitionTo(REMOVING);
download_manager_->RemoveDownload(db_handle_);
// We have now been deleted.
}
bool DownloadItem::TimeRemaining(base::TimeDelta* remaining) const {
if (total_bytes_ <= 0)
return false; // We never received the content_length for this download.
int64 speed = CurrentSpeed();
if (speed == 0)
return false;
*remaining = base::TimeDelta::FromSeconds(
(total_bytes_ - received_bytes_) / speed);
return true;
}
int64 DownloadItem::CurrentSpeed() const {
if (is_paused_)
return 0;
base::TimeDelta diff = base::TimeTicks::Now() - start_tick_;
int64 diff_ms = diff.InMilliseconds();
return diff_ms == 0 ? 0 : received_bytes_ * 1000 / diff_ms;
}
int DownloadItem::PercentComplete() const {
// If the delegate is delaying completion of the download, then we have no
// idea how long it will take.
if (delegate_delayed_complete_ || total_bytes_ <= 0)
return -1;
return static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
}
void DownloadItem::OnPathDetermined(const FilePath& path) {
full_path_ = path;
UpdateTarget();
}
void DownloadItem::Rename(const FilePath& full_path) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(20) << __FUNCTION__ << "()"
<< " full_path = \"" << full_path.value() << "\""
<< " " << DebugString(true);
DCHECK(!full_path.empty());
full_path_ = full_path;
}
void DownloadItem::TogglePause() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(IsInProgress());
if (is_paused_)
request_handle_.ResumeRequest();
else
request_handle_.PauseRequest();
is_paused_ = !is_paused_;
UpdateObservers();
}
void DownloadItem::OnDownloadCompleting(DownloadFileManager* file_manager) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(20) << __FUNCTION__ << "()"
<< " needs rename = " << NeedsRename()
<< " " << DebugString(true);
DCHECK_NE(DANGEROUS, safety_state());
DCHECK(file_manager);
if (NeedsRename()) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(file_manager,
&DownloadFileManager::RenameCompletingDownloadFile, id(),
GetTargetFilePath(), safety_state() == SAFE));
return;
}
Completed();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(file_manager, &DownloadFileManager::CompleteDownload,
id()));
}
void DownloadItem::OnDownloadRenamedToFinalName(const FilePath& full_path) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(20) << __FUNCTION__ << "()"
<< " full_path = \"" << full_path.value() << "\""
<< " needed rename = " << NeedsRename()
<< " " << DebugString(false);
DCHECK(NeedsRename());
Rename(full_path);
if (download_manager_->delegate()->ShouldCompleteDownload(this)) {
Completed();
} else {
delegate_delayed_complete_ = true;
}
}
bool DownloadItem::MatchesQuery(const string16& query) const {
if (query.empty())
return true;
DCHECK_EQ(query, base::i18n::ToLower(query));
string16 url_raw(base::i18n::ToLower(UTF8ToUTF16(GetURL().spec())));
if (url_raw.find(query) != string16::npos)
return true;
// TODO(phajdan.jr): write a test case for the following code.
// A good test case would be:
// "/\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xbd\xa0\xe5\xa5\xbd",
// L"/\x4f60\x597d\x4f60\x597d",
// "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD"
std::string languages;
TabContents* tab = request_handle_.GetTabContents();
if (tab)
languages = content::GetContentClient()->browser()->GetAcceptLangs(tab);
string16 url_formatted(
base::i18n::ToLower(net::FormatUrl(GetURL(), languages)));
if (url_formatted.find(query) != string16::npos)
return true;
string16 path(base::i18n::ToLower(full_path().LossyDisplayName()));
// This shouldn't just do a substring match; it is wrong for Unicode
// due to normalization and we have a fancier search-query system
// used elsewhere.
// http://code.google.com/p/chromium/issues/detail?id=71982
return (path.find(query) != string16::npos);
}
void DownloadItem::SetFileCheckResults(const DownloadStateInfo& state) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true);
state_info_ = state;
VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true);
UpdateSafetyState();
}
DownloadItem::DangerType DownloadItem::GetDangerType() const {
return ::GetDangerType(state_info_.is_dangerous_file,
state_info_.is_dangerous_url);
}
bool DownloadItem::IsDangerous() const {
return GetDangerType() != DownloadItem::NOT_DANGEROUS;
}
void DownloadItem::MarkFileDangerous() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
state_info_.is_dangerous_file = true;
UpdateSafetyState();
}
void DownloadItem::MarkUrlDangerous() {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
state_info_.is_dangerous_url = true;
UpdateSafetyState();
}
DownloadPersistentStoreInfo DownloadItem::GetPersistentStoreInfo() const {
return DownloadPersistentStoreInfo(full_path(),
GetURL(),
referrer_url(),
start_time(),
received_bytes(),
total_bytes(),
state(),
db_handle());
}
FilePath DownloadItem::GetTargetFilePath() const {
return full_path_.DirName().Append(state_info_.target_name);
}
FilePath DownloadItem::GetFileNameToReportUser() const {
if (state_info_.path_uniquifier > 0) {
FilePath name(state_info_.target_name);
DownloadFile::AppendNumberToPath(&name, state_info_.path_uniquifier);
return name;
}
return state_info_.target_name;
}
FilePath DownloadItem::GetUserVerifiedFilePath() const {
return (safety_state_ == DownloadItem::SAFE) ?
GetTargetFilePath() : full_path_;
}
void DownloadItem::OffThreadCancel(DownloadFileManager* file_manager) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
request_handle_.CancelRequest();
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(
file_manager, &DownloadFileManager::CancelDownload, download_id_));
}
void DownloadItem::Init(bool active) {
// TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UpdateTarget();
if (active) {
StartProgressTimer();
download_stats::RecordDownloadCount(download_stats::START_COUNT);
}
VLOG(20) << __FUNCTION__ << "() " << DebugString(true);
}
// TODO(ahendrickson) -- Move |INTERRUPTED| from |IsCancelled()| to
// |IsPartialDownload()|, when resuming interrupted downloads is implemented.
bool DownloadItem::IsPartialDownload() const {
return (state_ == IN_PROGRESS);
}
bool DownloadItem::IsInProgress() const {
return (state_ == IN_PROGRESS);
}
bool DownloadItem::IsCancelled() const {
return (state_ == CANCELLED) ||
(state_ == INTERRUPTED);
}
bool DownloadItem::IsInterrupted() const {
return (state_ == INTERRUPTED);
}
bool DownloadItem::IsComplete() const {
return (state_ == COMPLETE);
}
const GURL& DownloadItem::GetURL() const {
return url_chain_.empty() ?
GURL::EmptyGURL() : url_chain_.back();
}
std::string DownloadItem::DebugString(bool verbose) const {
std::string description =
base::StringPrintf("{ id = %d"
" state = %s",
download_id_,
DebugDownloadStateString(state()));
// Construct a string of the URL chain.
std::string url_list("<none>");
if (!url_chain_.empty()) {
std::vector<GURL>::const_iterator iter = url_chain_.begin();
std::vector<GURL>::const_iterator last = url_chain_.end();
url_list = (*iter).spec();
++iter;
for ( ; verbose && (iter != last); ++iter) {
url_list += " ->\n\t";
const GURL& next_url = *iter;
url_list += next_url.spec();
}
}
if (verbose) {
description += base::StringPrintf(
" db_handle = %" PRId64
" total_bytes = %" PRId64
" received_bytes = %" PRId64
" is_paused = %c"
" is_otr = %c"
" safety_state = %s"
" url_chain = \n\t\"%s\"\n\t"
" target_name = \"%" PRFilePath "\""
" full_path = \"%" PRFilePath "\"",
db_handle(),
total_bytes(),
received_bytes(),
is_paused() ? 'T' : 'F',
is_otr() ? 'T' : 'F',
DebugSafetyStateString(safety_state()),
url_list.c_str(),
state_info_.target_name.value().c_str(),
full_path().value().c_str());
} else {
description += base::StringPrintf(" url = \"%s\"", url_list.c_str());
}
description += " }";
return description;
}