blob: f313a98009b726ea8dfa63c2f6fb6902b5c693e9 [file] [log] [blame]
// Copyright (c) 2012 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_file_manager.h"
#include <set>
#include <string>
#include "base/bind.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/utf_string_conversions.h"
#include "content/browser/download/base_file.h"
#include "content/browser/download/download_create_info.h"
#include "content/browser/download/download_file_impl.h"
#include "content/browser/download/download_interrupt_reasons_impl.h"
#include "content/browser/download/download_request_handle.h"
#include "content/browser/download/download_stats.h"
#include "content/browser/power_save_blocker.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_manager_delegate.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
using content::BrowserThread;
using content::DownloadFile;
using content::DownloadId;
using content::DownloadManager;
namespace {
class DownloadFileFactoryImpl
: public DownloadFileManager::DownloadFileFactory {
public:
DownloadFileFactoryImpl() {}
virtual content::DownloadFile* CreateFile(
DownloadCreateInfo* info,
scoped_ptr<content::ByteStreamReader> stream,
const DownloadRequestHandle& request_handle,
DownloadManager* download_manager,
bool calculate_hash,
const net::BoundNetLog& bound_net_log) OVERRIDE;
};
DownloadFile* DownloadFileFactoryImpl::CreateFile(
DownloadCreateInfo* info,
scoped_ptr<content::ByteStreamReader> stream,
const DownloadRequestHandle& request_handle,
DownloadManager* download_manager,
bool calculate_hash,
const net::BoundNetLog& bound_net_log) {
return new DownloadFileImpl(
info, stream.Pass(), new DownloadRequestHandle(request_handle),
download_manager, calculate_hash,
scoped_ptr<PowerSaveBlocker>(
new PowerSaveBlocker(
PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep)).Pass(),
bound_net_log);
}
} // namespace
DownloadFileManager::DownloadFileManager(DownloadFileFactory* factory)
: download_file_factory_(factory) {
if (download_file_factory_ == NULL)
download_file_factory_.reset(new DownloadFileFactoryImpl);
}
DownloadFileManager::~DownloadFileManager() {
DCHECK(downloads_.empty());
}
void DownloadFileManager::Shutdown() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&DownloadFileManager::OnShutdown, this));
}
void DownloadFileManager::OnShutdown() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
STLDeleteValues(&downloads_);
}
void DownloadFileManager::CreateDownloadFile(
scoped_ptr<DownloadCreateInfo> info,
scoped_ptr<content::ByteStreamReader> stream,
const DownloadRequestHandle& request_handle,
DownloadManager* download_manager, bool get_hash,
const net::BoundNetLog& bound_net_log) {
DCHECK(info.get());
VLOG(20) << __FUNCTION__ << "()" << " info = " << info->DebugString();
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// Create the download file.
scoped_ptr<DownloadFile> download_file(download_file_factory_->CreateFile(
info.get(), stream.Pass(), request_handle, download_manager,
get_hash, bound_net_log));
net::Error init_result = download_file->Initialize();
if (net::OK != init_result) {
// Error: Handle via download manager/item.
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(
&DownloadManager::OnDownloadInterrupted,
download_manager,
info->download_id.local(),
0,
"",
content::ConvertNetErrorToInterruptReason(
init_result, content::DOWNLOAD_INTERRUPT_FROM_DISK)));
} else {
DCHECK(GetDownloadFile(info->download_id) == NULL);
downloads_[info->download_id] = download_file.release();
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DownloadManager::StartDownload, download_manager,
info->download_id.local()));
}
DownloadFile* DownloadFileManager::GetDownloadFile(
DownloadId global_id) {
DownloadFileMap::iterator it = downloads_.find(global_id);
return it == downloads_.end() ? NULL : it->second;
}
void DownloadFileManager::UpdateInProgressDownloads() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
for (DownloadFileMap::iterator i = downloads_.begin();
i != downloads_.end(); ++i) {
DownloadId global_id = i->first;
DownloadFile* download_file = i->second;
DownloadManager* manager = download_file->GetDownloadManager();
if (manager) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&DownloadManager::UpdateDownload,
manager,
global_id.local(),
download_file->BytesSoFar(),
download_file->CurrentSpeed(),
download_file->GetHashState()));
}
}
}
DownloadId DownloadFileManager::StartDownload(
scoped_ptr<DownloadCreateInfo> info,
scoped_ptr<content::ByteStreamReader> stream,
const DownloadRequestHandle& request_handle) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(info.get());
DownloadManager* manager = request_handle.GetDownloadManager();
DCHECK(manager); // Checked in |DownloadResourceHandler::StartOnUIThread()|.
// |bound_net_log| will be used for logging the both the download item's and
// the download file's events.
net::BoundNetLog bound_net_log =
manager->CreateDownloadItem(info.get(), request_handle);
DownloadId download_id = info->download_id;
bool hash_needed = manager->GenerateFileHash();
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&DownloadFileManager::CreateDownloadFile, this,
base::Passed(info.Pass()), base::Passed(stream.Pass()),
request_handle,
make_scoped_refptr(manager),
hash_needed, bound_net_log));
return download_id;
}
// This method will be sent via a user action, or shutdown on the UI thread, and
// run on the download thread. Since this message has been sent from the UI
// thread, the download may have already completed and won't exist in our map.
void DownloadFileManager::CancelDownload(DownloadId global_id) {
VLOG(20) << __FUNCTION__ << "()" << " id = " << global_id;
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DownloadFileMap::iterator it = downloads_.find(global_id);
if (it == downloads_.end())
return;
DownloadFile* download_file = it->second;
VLOG(20) << __FUNCTION__ << "()"
<< " download_file = " << download_file->DebugString();
download_file->Cancel();
EraseDownload(global_id);
}
void DownloadFileManager::CompleteDownload(DownloadId global_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
if (!ContainsKey(downloads_, global_id))
return;
DownloadFile* download_file = downloads_[global_id];
VLOG(20) << " " << __FUNCTION__ << "()"
<< " id = " << global_id
<< " download_file = " << download_file->DebugString();
download_file->Detach();
EraseDownload(global_id);
}
void DownloadFileManager::OnDownloadManagerShutdown(DownloadManager* manager) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(manager);
std::set<DownloadFile*> to_remove;
for (DownloadFileMap::iterator i = downloads_.begin();
i != downloads_.end(); ++i) {
DownloadFile* download_file = i->second;
if (download_file->GetDownloadManager() == manager) {
download_file->CancelDownloadRequest();
to_remove.insert(download_file);
}
}
for (std::set<DownloadFile*>::iterator i = to_remove.begin();
i != to_remove.end(); ++i) {
downloads_.erase((*i)->GlobalId());
delete *i;
}
}
// Actions from the UI thread and run on the download thread
// The DownloadManager in the UI thread has provided an intermediate .crdownload
// name for the download specified by 'id'. Rename the in progress download.
//
// There are 2 possible rename cases where this method can be called:
// 1. tmp -> foo.crdownload (not final, safe)
// 2. tmp-> Unconfirmed.xxx.crdownload (not final, dangerous)
// TODO(asanka): Merge with RenameCompletingDownloadFile() and move
// uniquification logic into DownloadFile.
void DownloadFileManager::RenameInProgressDownloadFile(
DownloadId global_id,
const FilePath& full_path,
bool overwrite_existing_file,
const RenameCompletionCallback& callback) {
VLOG(20) << __FUNCTION__ << "()" << " id = " << global_id
<< " full_path = \"" << full_path.value() << "\"";
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DownloadFile* download_file = GetDownloadFile(global_id);
if (!download_file) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(callback, FilePath()));
return;
}
VLOG(20) << __FUNCTION__ << "()"
<< " download_file = " << download_file->DebugString();
FilePath new_path(full_path);
if (!overwrite_existing_file) {
int uniquifier =
file_util::GetUniquePathNumber(new_path, FILE_PATH_LITERAL(""));
if (uniquifier > 0) {
new_path = new_path.InsertBeforeExtensionASCII(
StringPrintf(" (%d)", uniquifier));
}
}
net::Error rename_error = download_file->Rename(new_path);
if (net::OK != rename_error) {
CancelDownloadOnRename(global_id, rename_error);
new_path.clear();
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(callback, new_path));
}
// The DownloadManager in the UI thread has provided a final name for the
// download specified by 'id'. Rename the download that's in the process
// of completing.
//
// There are 2 possible rename cases where this method can be called:
// 1. foo.crdownload -> foo (final, safe)
// 2. Unconfirmed.xxx.crdownload -> xxx (final, validated)
void DownloadFileManager::RenameCompletingDownloadFile(
DownloadId global_id,
const FilePath& full_path,
bool overwrite_existing_file,
const RenameCompletionCallback& callback) {
VLOG(20) << __FUNCTION__ << "()" << " id = " << global_id
<< " overwrite_existing_file = " << overwrite_existing_file
<< " full_path = \"" << full_path.value() << "\"";
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DownloadFile* download_file = GetDownloadFile(global_id);
if (!download_file) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(callback, FilePath()));
return;
}
VLOG(20) << __FUNCTION__ << "()"
<< " download_file = " << download_file->DebugString();
FilePath new_path = full_path;
if (!overwrite_existing_file) {
// Make our name unique at this point, as if a dangerous file is
// downloading and a 2nd download is started for a file with the same
// name, they would have the same path. This is because we uniquify
// the name on download start, and at that time the first file does
// not exists yet, so the second file gets the same name.
// This should not happen in the SAFE case, and we check for that in the UI
// thread.
int uniquifier =
file_util::GetUniquePathNumber(new_path, FILE_PATH_LITERAL(""));
if (uniquifier > 0) {
new_path = new_path.InsertBeforeExtensionASCII(
StringPrintf(" (%d)", uniquifier));
}
}
// Rename the file, overwriting if necessary.
net::Error rename_error = download_file->Rename(new_path);
if (net::OK != rename_error) {
// Error. Between the time the UI thread generated 'full_path' to the time
// this code runs, something happened that prevents us from renaming.
CancelDownloadOnRename(global_id, rename_error);
new_path.clear();
} else {
#if defined(OS_MACOSX)
// Done here because we only want to do this once; see
// http://crbug.com/13120 for details.
download_file->AnnotateWithSourceInformation();
#endif
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(callback, new_path));
}
int DownloadFileManager::NumberOfActiveDownloads() const {
return downloads_.size();
}
// Called only from RenameInProgressDownloadFile and
// RenameCompletingDownloadFile on the FILE thread.
// TODO(asanka): Use the RenameCompletionCallback instead of a separate
// OnDownloadInterrupted call.
void DownloadFileManager::CancelDownloadOnRename(
DownloadId global_id, net::Error rename_error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DownloadFile* download_file = GetDownloadFile(global_id);
if (!download_file)
return;
DownloadManager* download_manager = download_file->GetDownloadManager();
if (!download_manager) {
// Without a download manager, we can't cancel the request normally, so we
// need to do it here. The normal path will also update the download
// history before canceling the request.
download_file->CancelDownloadRequest();
return;
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DownloadManager::OnDownloadInterrupted,
download_manager,
global_id.local(),
download_file->BytesSoFar(),
download_file->GetHashState(),
content::ConvertNetErrorToInterruptReason(
rename_error,
content::DOWNLOAD_INTERRUPT_FROM_DISK)));
}
void DownloadFileManager::EraseDownload(DownloadId global_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
if (!ContainsKey(downloads_, global_id))
return;
DownloadFile* download_file = downloads_[global_id];
VLOG(20) << " " << __FUNCTION__ << "()"
<< " id = " << global_id
<< " download_file = " << download_file->DebugString();
downloads_.erase(global_id);
delete download_file;
}