// 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/message_loop/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" namespace content { namespace { typedef base::Callback<void(bool)> OnCompleted; } // namespace // On windows, DragDownloadFile runs on a thread other than the UI thread. // DownloadItem and DownloadManager may not be accessed on any thread other than // the UI thread. DragDownloadFile may run on either the "drag" thread or the UI // thread depending on the platform, but DragDownloadFileUI strictly always runs // on the UI thread. On platforms where DragDownloadFile runs on the UI thread, // none of the PostTasks are necessary, but it simplifies the code to do them // anyway. class DragDownloadFile::DragDownloadFileUI : public DownloadItem::Observer { public: DragDownloadFileUI(const GURL& url, const Referrer& referrer, const std::string& referrer_encoding, WebContents* web_contents, base::MessageLoop* on_completed_loop, const OnCompleted& on_completed) : on_completed_loop_(on_completed_loop), on_completed_(on_completed), url_(url), referrer_(referrer), referrer_encoding_(referrer_encoding), web_contents_(web_contents), download_item_(NULL), weak_ptr_factory_(this) { DCHECK(on_completed_loop_); DCHECK(!on_completed_.is_null()); DCHECK(web_contents_); // May be called on any thread. // Do not call weak_ptr_factory_.GetWeakPtr() outside the UI thread. } void InitiateDownload(scoped_ptr<net::FileStream> file_stream, const base::FilePath& file_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DownloadManager* download_manager = BrowserContext::GetDownloadManager(web_contents_->GetBrowserContext()); RecordDownloadSource(INITIATED_BY_DRAG_N_DROP); scoped_ptr<content::DownloadUrlParameters> params( DownloadUrlParameters::FromWebContents(web_contents_, url_)); params->set_referrer(referrer_); params->set_referrer_encoding(referrer_encoding_); params->set_callback(base::Bind(&DragDownloadFileUI::OnDownloadStarted, weak_ptr_factory_.GetWeakPtr())); params->set_file_path(file_path); params->set_file_stream(file_stream.Pass()); // Nulls file_stream. download_manager->DownloadUrl(params.Pass()); } void Cancel() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (download_item_) download_item_->Cancel(true); } void Delete() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); delete this; } private: virtual ~DragDownloadFileUI() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (download_item_) download_item_->RemoveObserver(this); } void OnDownloadStarted(DownloadItem* item, net::Error error) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!item) { DCHECK_NE(net::OK, error); on_completed_loop_->PostTask(FROM_HERE, base::Bind(on_completed_, false)); return; } DCHECK_EQ(net::OK, error); download_item_ = item; download_item_->AddObserver(this); } // DownloadItem::Observer: virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK_EQ(download_item_, item); DownloadItem::DownloadState state = download_item_->GetState(); if (state == DownloadItem::COMPLETE || state == DownloadItem::CANCELLED || state == DownloadItem::INTERRUPTED) { if (!on_completed_.is_null()) { on_completed_loop_->PostTask(FROM_HERE, base::Bind( on_completed_, state == DownloadItem::COMPLETE)); on_completed_.Reset(); } download_item_->RemoveObserver(this); download_item_ = NULL; } // Ignore other states. } virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK_EQ(download_item_, item); if (!on_completed_.is_null()) { const bool is_complete = download_item_->GetState() == DownloadItem::COMPLETE; on_completed_loop_->PostTask(FROM_HERE, base::Bind( on_completed_, is_complete)); on_completed_.Reset(); } download_item_->RemoveObserver(this); download_item_ = NULL; } base::MessageLoop* on_completed_loop_; OnCompleted on_completed_; GURL url_; Referrer referrer_; std::string referrer_encoding_; WebContents* web_contents_; DownloadItem* download_item_; // Only used in the callback from DownloadManager::DownloadUrl(). base::WeakPtrFactory<DragDownloadFileUI> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(DragDownloadFileUI); }; DragDownloadFile::DragDownloadFile(const base::FilePath& file_path, scoped_ptr<net::FileStream> file_stream, const GURL& url, const Referrer& referrer, const std::string& referrer_encoding, WebContents* web_contents) : file_path_(file_path), file_stream_(file_stream.Pass()), drag_message_loop_(base::MessageLoop::current()), state_(INITIALIZED), drag_ui_(NULL), weak_ptr_factory_(this) { drag_ui_ = new DragDownloadFileUI( url, referrer, referrer_encoding, web_contents, drag_message_loop_, base::Bind(&DragDownloadFile::DownloadCompleted, weak_ptr_factory_.GetWeakPtr())); DCHECK(!file_path_.empty()); } DragDownloadFile::~DragDownloadFile() { CheckThread(); // This is the only place that drag_ui_ can be deleted from. Post a message to // the UI thread so that it calls RemoveObserver on the right thread, and so // that this task will run after the InitiateDownload task runs on the UI // thread. BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( &DragDownloadFileUI::Delete, base::Unretained(drag_ui_))); drag_ui_ = NULL; } void DragDownloadFile::Start(ui::DownloadFileObserver* observer) { CheckThread(); if (state_ != INITIALIZED) return; state_ = STARTED; DCHECK(!observer_.get()); observer_ = observer; DCHECK(observer_.get()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( &DragDownloadFileUI::InitiateDownload, base::Unretained(drag_ui_), base::Passed(&file_stream_), file_path_)); } bool DragDownloadFile::Wait() { CheckThread(); if (state_ == STARTED) nested_loop_.Run(); return state_ == SUCCESS; } void DragDownloadFile::Stop() { CheckThread(); if (drag_ui_) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( &DragDownloadFileUI::Cancel, base::Unretained(drag_ui_))); } } void DragDownloadFile::DownloadCompleted(bool is_successful) { CheckThread(); state_ = is_successful ? SUCCESS : FAILURE; if (is_successful) observer_->OnDownloadCompleted(file_path_); else observer_->OnDownloadAborted(); // Release the observer since we do not need it any more. observer_ = NULL; if (nested_loop_.running()) nested_loop_.Quit(); } void DragDownloadFile::CheckThread() { #if defined(OS_WIN) DCHECK(drag_message_loop_ == base::MessageLoop::current()); #else DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); #endif } } // namespace content