| // 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/files/file.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" |
| |
| 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(base::File file, |
| 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(file.Pass()); // Nulls file. |
| 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: |
| ~DragDownloadFileUI() override { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (download_item_) |
| download_item_->RemoveObserver(this); |
| } |
| |
| void OnDownloadStarted(DownloadItem* item, |
| DownloadInterruptReason interrupt_reason) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!item) { |
| DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); |
| on_completed_loop_->PostTask(FROM_HERE, base::Bind(on_completed_, false)); |
| return; |
| } |
| DCHECK_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); |
| download_item_ = item; |
| download_item_->AddObserver(this); |
| } |
| |
| // DownloadItem::Observer: |
| 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. |
| } |
| |
| 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, |
| base::File file, |
| const GURL& url, |
| const Referrer& referrer, |
| const std::string& referrer_encoding, |
| WebContents* web_contents) |
| : file_path_(file_path), |
| file_(file.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_), 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 |