blob: e66b2016a2471ad7a00fb08f0292005135dbc074 [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/drag_download_file.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "content/browser/download/download_stats.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_save_info.h"
#include "content/public/browser/download_url_parameters.h"
#include "net/base/file_stream.h"
using content::BrowserContext;
using content::BrowserThread;
using content::DownloadItem;
using content::DownloadManager;
using content::DownloadUrlParameters;
using content::WebContents;
DragDownloadFile::DragDownloadFile(
const FilePath& file_name_or_path,
linked_ptr<net::FileStream> file_stream,
const GURL& url,
const content::Referrer& referrer,
const std::string& referrer_encoding,
WebContents* web_contents)
: file_stream_(file_stream),
url_(url),
referrer_(referrer),
referrer_encoding_(referrer_encoding),
web_contents_(web_contents),
drag_message_loop_(MessageLoop::current()),
is_started_(false),
is_successful_(false),
#if defined(OS_WIN)
is_running_nested_message_loop_(false),
#endif
download_manager_(NULL),
download_manager_observer_added_(false),
download_item_(NULL) {
#if defined(OS_WIN)
DCHECK(!file_name_or_path.empty() && !file_stream.get());
file_name_ = file_name_or_path;
#elif defined(OS_POSIX)
DCHECK(!file_name_or_path.empty() && file_stream.get());
file_path_ = file_name_or_path;
#endif
}
DragDownloadFile::~DragDownloadFile() {
AssertCurrentlyOnDragThread();
// Since the target application can still hold and use the dragged file,
// we do not know the time that it can be safely deleted. To solve this
// problem, we schedule it to be removed after the system is restarted.
#if defined(OS_WIN)
if (!temp_dir_path_.empty()) {
if (!file_path_.empty())
file_util::DeleteAfterReboot(file_path_);
file_util::DeleteAfterReboot(temp_dir_path_);
}
#endif
RemoveObservers();
}
void DragDownloadFile::RemoveObservers() {
if (download_item_) {
download_item_->RemoveObserver(this);
download_item_ = NULL;
}
if (download_manager_observer_added_) {
download_manager_observer_added_ = false;
download_manager_->RemoveObserver(this);
}
}
bool DragDownloadFile::Start(ui::DownloadFileObserver* observer) {
AssertCurrentlyOnDragThread();
if (is_started_)
return true;
is_started_ = true;
DCHECK(!observer_.get());
observer_ = observer;
if (!file_stream_.get()) {
// Create a temporary directory to save the temporary download file. We do
// not want to use the default download directory since we do not want the
// twisted file name shown in the download shelf if the file with the same
// name already exists.
if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome"),
&temp_dir_path_))
return false;
file_path_ = temp_dir_path_.Append(file_name_);
}
InitiateDownload();
// On Windows, we need to wait till the download file is completed.
#if defined(OS_WIN)
StartNestedMessageLoop();
#endif
return is_successful_;
}
void DragDownloadFile::Stop() {
}
void DragDownloadFile::InitiateDownload() {
#if defined(OS_WIN)
// DownloadManager could only be invoked from the UI thread.
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DragDownloadFile::InitiateDownload, this));
return;
}
#endif
download_manager_ = BrowserContext::GetDownloadManager(
web_contents_->GetBrowserContext());
download_manager_observer_added_ = true;
download_manager_->AddObserver(this);
content::DownloadSaveInfo save_info;
save_info.file_path = file_path_;
save_info.file_stream = file_stream_;
download_stats::RecordDownloadSource(
download_stats::INITIATED_BY_DRAG_N_DROP);
scoped_ptr<DownloadUrlParameters> params(
DownloadUrlParameters::FromWebContents(web_contents_, url_, save_info));
params->set_referrer(referrer_);
params->set_referrer_encoding(referrer_encoding_);
download_manager_->DownloadUrl(params.Pass());
}
void DragDownloadFile::DownloadCompleted(bool is_successful) {
#if defined(OS_WIN)
// If not in drag-and-drop thread, defer the running to it.
if (drag_message_loop_ != MessageLoop::current()) {
drag_message_loop_->PostTask(
FROM_HERE,
base::Bind(&DragDownloadFile::DownloadCompleted, this, is_successful));
return;
}
#endif
is_successful_ = is_successful;
// Call the observer.
DCHECK(observer_);
if (is_successful)
observer_->OnDownloadCompleted(file_path_);
else
observer_->OnDownloadAborted();
// Release the observer since we do not need it any more.
observer_ = NULL;
// On Windows, we need to stop the waiting.
#if defined(OS_WIN)
QuitNestedMessageLoop();
#endif
}
void DragDownloadFile::ModelChanged(DownloadManager* manager) {
AssertCurrentlyOnUIThread();
DCHECK_EQ(manager, download_manager_);
if (download_item_)
return;
std::vector<DownloadItem*> downloads;
download_manager_->GetAllDownloads(&downloads);
for (std::vector<DownloadItem*>::const_iterator i = downloads.begin();
i != downloads.end(); ++i) {
DownloadItem* item = *i;
if (item->IsTemporary() &&
item->GetOriginalUrl() == url_ &&
file_path_.DirName() == item->GetTargetFilePath().DirName()) {
download_item_ = item;
download_item_->AddObserver(this);
break;
}
}
}
void DragDownloadFile::OnDownloadUpdated(content::DownloadItem* download) {
AssertCurrentlyOnUIThread();
if (download->IsCancelled()) {
RemoveObservers();
DownloadCompleted(false);
} else if (download->IsComplete()) {
RemoveObservers();
DownloadCompleted(true);
}
// Ignore other states.
}
// If the download completes or is cancelled, then OnDownloadUpdated() will
// handle it and RemoveObserver() so that OnDownloadDestroyed is never called.
// OnDownloadDestroyed is only called if OnDownloadUpdated() does not detect
// completion or cancellation (in which cases it removes this observer).
// TODO(benjhayden): Try to change this to NOTREACHED()?
void DragDownloadFile::OnDownloadDestroyed(content::DownloadItem* download) {
AssertCurrentlyOnUIThread();
RemoveObservers();
DownloadCompleted(false);
}
void DragDownloadFile::AssertCurrentlyOnDragThread() {
// Only do the check on Windows where two threads are involved.
#if defined(OS_WIN)
DCHECK(drag_message_loop_ == MessageLoop::current());
#endif
}
void DragDownloadFile::AssertCurrentlyOnUIThread() {
// Only do the check on Windows where two threads are involved.
#if defined(OS_WIN)
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
#endif
}
#if defined(OS_WIN)
void DragDownloadFile::StartNestedMessageLoop() {
AssertCurrentlyOnDragThread();
MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
is_running_nested_message_loop_ = true;
MessageLoop::current()->Run();
DCHECK(!is_running_nested_message_loop_);
}
void DragDownloadFile::QuitNestedMessageLoop() {
AssertCurrentlyOnDragThread();
if (is_running_nested_message_loop_) {
is_running_nested_message_loop_ = false;
MessageLoop::current()->Quit();
}
}
#endif