blob: f6bee14b83bd482addfac21ec8bc31f49451465d [file] [log] [blame]
// Copyright (c) 2010 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 "chrome/browser/download/download_item.h"
#include "app/l10n_util.h"
#include "base/basictypes.h"
#include "base/file_util.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "net/base/net_util.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/download/download_extensions.h"
#include "chrome/browser/download/download_file_manager.h"
#include "chrome/browser/download/download_history.h"
#include "chrome/browser/download/download_manager.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_util.h"
#include "chrome/browser/history/download_create_info.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/pref_names.h"
namespace {
// Update frequency (milliseconds).
const int kUpdateTimeMs = 1000;
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";
default:
NOTREACHED() << "Unknown download state " << state;
return "unknown";
};
}
} // namespace
// Constructor for reading from the history service.
DownloadItem::DownloadItem(DownloadManager* download_manager,
const DownloadCreateInfo& info)
: id_(-1),
full_path_(info.path),
path_uniquifier_(0),
url_(info.url),
referrer_url_(info.referrer_url),
mime_type_(info.mime_type),
original_mime_type_(info.original_mime_type),
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),
safety_state_(SAFE),
auto_opened_(false),
target_name_(info.original_name),
render_process_id_(-1),
request_id_(-1),
save_as_(false),
is_otr_(false),
is_extension_install_(info.is_extension_install),
name_finalized_(false),
is_temporary_(false),
opened_(false) {
if (state_ == IN_PROGRESS)
state_ = CANCELLED;
Init(false /* don't start progress timer */);
}
// Constructing for a regular download:
DownloadItem::DownloadItem(DownloadManager* download_manager,
const DownloadCreateInfo& info,
bool is_otr)
: id_(info.download_id),
full_path_(info.path),
path_uniquifier_(info.path_uniquifier),
url_(info.url),
referrer_url_(info.referrer_url),
mime_type_(info.mime_type),
original_mime_type_(info.original_mime_type),
total_bytes_(info.total_bytes),
received_bytes_(0),
start_tick_(base::TimeTicks::Now()),
state_(IN_PROGRESS),
start_time_(info.start_time),
db_handle_(DownloadHistory::kUninitializedHandle),
download_manager_(download_manager),
is_paused_(false),
open_when_complete_(false),
safety_state_(info.is_dangerous ? DANGEROUS : SAFE),
auto_opened_(false),
target_name_(info.original_name),
render_process_id_(info.child_id),
request_id_(info.request_id),
save_as_(info.prompt_user_for_save_location),
is_otr_(is_otr),
is_extension_install_(info.is_extension_install),
name_finalized_(false),
is_temporary_(!info.save_info.file_path.empty()),
opened_(false) {
Init(true /* start progress timer */);
}
// Constructing for the "Save Page As..." feature:
DownloadItem::DownloadItem(DownloadManager* download_manager,
const FilePath& path,
const GURL& url,
bool is_otr)
: id_(1),
full_path_(path),
path_uniquifier_(0),
url_(url),
referrer_url_(GURL()),
mime_type_(std::string()),
original_mime_type_(std::string()),
total_bytes_(0),
received_bytes_(0),
start_tick_(base::TimeTicks::Now()),
state_(IN_PROGRESS),
start_time_(base::Time::Now()),
db_handle_(DownloadHistory::kUninitializedHandle),
download_manager_(download_manager),
is_paused_(false),
open_when_complete_(false),
safety_state_(SAFE),
auto_opened_(false),
render_process_id_(-1),
request_id_(-1),
save_as_(false),
is_otr_(is_otr),
is_extension_install_(false),
name_finalized_(false),
is_temporary_(false),
opened_(false) {
Init(true /* start progress timer */);
}
DownloadItem::~DownloadItem() {
state_ = REMOVING;
UpdateObservers();
}
void DownloadItem::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DownloadItem::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void DownloadItem::UpdateObservers() {
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this));
}
void DownloadItem::NotifyObserversDownloadFileCompleted() {
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadFileCompleted(this));
}
bool DownloadItem::CanOpenDownload() {
return !Extension::IsExtension(target_name_) &&
download_util::IsFileSafe(target_name_);
}
bool DownloadItem::ShouldOpenFileBasedOnExtension() {
return download_manager_->ShouldOpenFileBasedOnExtension(
GetUserVerifiedFilePath());
}
void DownloadItem::OpenFilesBasedOnExtension(bool open) {
DownloadPrefs* prefs = download_manager_->download_prefs();
if (open)
prefs->EnableAutoOpenBasedOnExtension(GetUserVerifiedFilePath());
else
prefs->DisableAutoOpenBasedOnExtension(GetUserVerifiedFilePath());
}
void DownloadItem::OpenDownload() {
if (state() == DownloadItem::IN_PROGRESS) {
open_when_complete_ = !open_when_complete_;
} else if (state() == DownloadItem::COMPLETE) {
opened_ = true;
FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this));
if (is_extension_install()) {
download_util::OpenChromeExtension(download_manager_->profile(),
download_manager_,
*this);
return;
}
#if defined(OS_MACOSX)
// Mac OS X requires opening downloads on the UI thread.
platform_util::OpenItem(full_path());
#else
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableFunction(&platform_util::OpenItem, full_path()));
#endif
}
}
void DownloadItem::ShowDownloadInShell() {
#if defined(OS_MACOSX)
// Mac needs to run this operation on the UI thread.
platform_util::ShowItemInFolder(full_path());
#else
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableFunction(&platform_util::ShowItemInFolder,
full_path()));
#endif
}
void DownloadItem::DangerousDownloadValidated() {
download_manager_->DangerousDownloadValidated(this);
}
void DownloadItem::UpdateSize(int64 bytes_so_far) {
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() {
update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdateTimeMs), this,
&DownloadItem::UpdateObservers);
}
void DownloadItem::StopProgressTimer() {
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) {
if (state_ == COMPLETE) {
NOTREACHED();
return;
}
UpdateSize(bytes_so_far);
UpdateObservers();
}
// Triggered by a user action.
void DownloadItem::Cancel(bool update_history) {
VLOG(20) << __FUNCTION__ << "()" << " download = " << DebugString(true);
if (state_ != IN_PROGRESS) {
// Small downloads might be complete before this method has a chance to run.
return;
}
state_ = CANCELLED;
UpdateObservers();
StopProgressTimer();
if (update_history)
download_manager_->DownloadCancelled(id_);
}
void DownloadItem::OnAllDataSaved(int64 size) {
state_ = COMPLETE;
UpdateSize(size);
StopProgressTimer();
}
void DownloadItem::Finished() {
// Handle chrome extensions explicitly and skip the shell execute.
if (is_extension_install()) {
download_util::OpenChromeExtension(download_manager_->profile(),
download_manager_,
*this);
auto_opened_ = true;
} else if (open_when_complete() ||
download_manager_->ShouldOpenFileBasedOnExtension(
GetUserVerifiedFilePath()) ||
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;
}
// Notify our observers that we are complete (the call to OnAllDataSaved()
// set the state to complete but did not notify).
UpdateObservers();
// The download file is meant to be completed if both the filename is
// finalized and the file data is downloaded. The ordering of these two
// actions is indeterministic. Thus, if the filename is not finalized yet,
// delay the notification.
if (name_finalized())
NotifyObserversDownloadFileCompleted();
}
void DownloadItem::Remove(bool delete_on_disk) {
Cancel(true);
state_ = REMOVING;
if (delete_on_disk) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableFunction(&DeleteDownloadedFile, full_path_));
}
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 {
int percent = -1;
if (total_bytes_ > 0)
percent = static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
return percent;
}
void DownloadItem::Rename(const FilePath& full_path) {
VLOG(20) << " " << __FUNCTION__ << "()"
<< " full_path = " << full_path.value()
<< DebugString(true);
DCHECK(!full_path.empty());
full_path_ = full_path;
}
void DownloadItem::TogglePause() {
DCHECK(state_ == IN_PROGRESS);
download_manager_->PauseDownload(id_, !is_paused_);
is_paused_ = !is_paused_;
UpdateObservers();
}
void DownloadItem::OnNameFinalized() {
name_finalized_ = true;
// The download file is meant to be completed if both the filename is
// finalized and the file data is downloaded. The ordering of these two
// actions is indeterministic. Thus, if we are still in downloading the
// file, delay the notification.
if (state() == DownloadItem::COMPLETE)
NotifyObserversDownloadFileCompleted();
}
void DownloadItem::OnSafeDownloadFinished(DownloadFileManager* file_manager) {
DCHECK_EQ(SAFE, safety_state());
DCHECK(file_manager);
if (NeedsRename()) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(
file_manager, &DownloadFileManager::OnFinalDownloadName,
id(), GetTargetFilePath(), false,
make_scoped_refptr(download_manager_)));
return;
}
Finished();
}
void DownloadItem::OnDownloadRenamedToFinalName(const FilePath& full_path) {
VLOG(20) << " " << __FUNCTION__ << "()"
<< " full_path = " << full_path.value();
bool needed_rename = NeedsRename();
Rename(full_path);
OnNameFinalized();
if (needed_rename && safety_state() == SAFE) {
// This was called from OnSafeDownloadFinished; continue to call
// DownloadFinished.
Finished();
}
}
bool DownloadItem::MatchesQuery(const string16& query) const {
if (query.empty())
return true;
DCHECK_EQ(query, l10n_util::ToLower(query));
string16 url_raw(l10n_util::ToLower(UTF8ToUTF16(url_.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"
PrefService* prefs = download_manager_->profile()->GetPrefs();
std::string languages(prefs->GetString(prefs::kAcceptLanguages));
string16 url_formatted(l10n_util::ToLower(net::FormatUrl(url_, languages)));
if (url_formatted.find(query) != string16::npos)
return true;
string16 path(l10n_util::ToLower(WideToUTF16(full_path().ToWStringHack())));
if (path.find(query) != std::wstring::npos)
return true;
return false;
}
void DownloadItem::SetFileCheckResults(const FilePath& path,
bool is_dangerous,
int path_uniquifier,
bool prompt,
bool is_extension_install,
const FilePath& original_name) {
VLOG(20) << " " << __FUNCTION__ << "()"
<< " path = \"" << path.value() << "\""
<< " is_dangerous = " << is_dangerous
<< " path_uniquifier = " << path_uniquifier
<< " prompt = " << prompt
<< " is_extension_install = " << is_extension_install
<< " path = \"" << path.value() << "\""
<< " original_name = \"" << original_name.value() << "\""
<< " " << DebugString(true);
// Make sure the initial file name is set only once.
DCHECK(full_path_.empty());
DCHECK(!path.empty());
full_path_ = path;
safety_state_ = is_dangerous ? DANGEROUS : SAFE;
path_uniquifier_ = path_uniquifier;
save_as_ = prompt;
is_extension_install_ = is_extension_install;
target_name_ = original_name;
if (target_name_.value().empty())
target_name_ = full_path_.BaseName();
}
FilePath DownloadItem::GetTargetFilePath() const {
return full_path_.DirName().Append(target_name_);
}
FilePath DownloadItem::GetFileNameToReportUser() const {
if (path_uniquifier_ > 0) {
FilePath name(target_name_);
download_util::AppendNumberToPath(&name, path_uniquifier_);
return name;
}
return target_name_;
}
FilePath DownloadItem::GetUserVerifiedFilePath() const {
if (safety_state_ == DownloadItem::SAFE)
return GetTargetFilePath();
return full_path_;
}
void DownloadItem::Init(bool start_timer) {
if (target_name_.value().empty())
target_name_ = full_path_.BaseName();
if (start_timer)
StartProgressTimer();
VLOG(20) << " " << __FUNCTION__ << "() " << DebugString(true);
}
std::string DownloadItem::DebugString(bool verbose) const {
std::string description =
base::StringPrintf("{ id_ = %d"
" state = %s",
id_,
DebugDownloadStateString(state()));
if (verbose) {
description += base::StringPrintf(
" db_handle = %" PRId64
" total_bytes = %" PRId64
" is_paused = " "%c"
" is_extension_install = " "%c"
" is_otr = " "%c"
" safety_state = " "%s"
" url = " "\"%s\""
" target_name_ = \"%" PRFilePath "\""
" full_path = \"%" PRFilePath "\"",
db_handle(),
total_bytes(),
is_paused() ? 'T' : 'F',
is_extension_install() ? 'T' : 'F',
is_otr() ? 'T' : 'F',
DebugSafetyStateString(safety_state()),
url().spec().c_str(),
target_name_.value().c_str(),
full_path().value().c_str());
} else {
description += base::StringPrintf(" url = \"%s\"", url().spec().c_str());
}
description += " }";
return description;
}