1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/download/drag_download_file.h" 6 7 #include "base/bind.h" 8 #include "base/message_loop/message_loop.h" 9 #include "content/browser/download/download_stats.h" 10 #include "content/browser/web_contents/web_contents_impl.h" 11 #include "content/public/browser/browser_context.h" 12 #include "content/public/browser/browser_thread.h" 13 #include "content/public/browser/download_item.h" 14 #include "content/public/browser/download_save_info.h" 15 #include "content/public/browser/download_url_parameters.h" 16 #include "net/base/file_stream.h" 17 18 namespace content { 19 20 namespace { 21 22 typedef base::Callback<void(bool)> OnCompleted; 23 24 } // namespace 25 26 // On windows, DragDownloadFile runs on a thread other than the UI thread. 27 // DownloadItem and DownloadManager may not be accessed on any thread other than 28 // the UI thread. DragDownloadFile may run on either the "drag" thread or the UI 29 // thread depending on the platform, but DragDownloadFileUI strictly always runs 30 // on the UI thread. On platforms where DragDownloadFile runs on the UI thread, 31 // none of the PostTasks are necessary, but it simplifies the code to do them 32 // anyway. 33 class DragDownloadFile::DragDownloadFileUI : public DownloadItem::Observer { 34 public: 35 DragDownloadFileUI(const GURL& url, 36 const Referrer& referrer, 37 const std::string& referrer_encoding, 38 WebContents* web_contents, 39 base::MessageLoop* on_completed_loop, 40 const OnCompleted& on_completed) 41 : on_completed_loop_(on_completed_loop), 42 on_completed_(on_completed), 43 url_(url), 44 referrer_(referrer), 45 referrer_encoding_(referrer_encoding), 46 web_contents_(web_contents), 47 download_item_(NULL), 48 weak_ptr_factory_(this) { 49 DCHECK(on_completed_loop_); 50 DCHECK(!on_completed_.is_null()); 51 DCHECK(web_contents_); 52 // May be called on any thread. 53 // Do not call weak_ptr_factory_.GetWeakPtr() outside the UI thread. 54 } 55 56 void InitiateDownload(scoped_ptr<net::FileStream> file_stream, 57 const base::FilePath& file_path) { 58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 59 DownloadManager* download_manager = 60 BrowserContext::GetDownloadManager(web_contents_->GetBrowserContext()); 61 62 RecordDownloadSource(INITIATED_BY_DRAG_N_DROP); 63 scoped_ptr<content::DownloadUrlParameters> params( 64 DownloadUrlParameters::FromWebContents(web_contents_, url_)); 65 params->set_referrer(referrer_); 66 params->set_referrer_encoding(referrer_encoding_); 67 params->set_callback(base::Bind(&DragDownloadFileUI::OnDownloadStarted, 68 weak_ptr_factory_.GetWeakPtr())); 69 params->set_file_path(file_path); 70 params->set_file_stream(file_stream.Pass()); // Nulls file_stream. 71 download_manager->DownloadUrl(params.Pass()); 72 } 73 74 void Cancel() { 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 76 if (download_item_) 77 download_item_->Cancel(true); 78 } 79 80 void Delete() { 81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 82 delete this; 83 } 84 85 private: 86 virtual ~DragDownloadFileUI() { 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 88 if (download_item_) 89 download_item_->RemoveObserver(this); 90 } 91 92 void OnDownloadStarted(DownloadItem* item, net::Error error) { 93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 94 if (!item) { 95 DCHECK_NE(net::OK, error); 96 on_completed_loop_->PostTask(FROM_HERE, base::Bind(on_completed_, false)); 97 return; 98 } 99 DCHECK_EQ(net::OK, error); 100 download_item_ = item; 101 download_item_->AddObserver(this); 102 } 103 104 // DownloadItem::Observer: 105 virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE { 106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 107 DCHECK_EQ(download_item_, item); 108 DownloadItem::DownloadState state = download_item_->GetState(); 109 if (state == DownloadItem::COMPLETE || 110 state == DownloadItem::CANCELLED || 111 state == DownloadItem::INTERRUPTED) { 112 if (!on_completed_.is_null()) { 113 on_completed_loop_->PostTask(FROM_HERE, base::Bind( 114 on_completed_, state == DownloadItem::COMPLETE)); 115 on_completed_.Reset(); 116 } 117 download_item_->RemoveObserver(this); 118 download_item_ = NULL; 119 } 120 // Ignore other states. 121 } 122 123 virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE { 124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 125 DCHECK_EQ(download_item_, item); 126 if (!on_completed_.is_null()) { 127 const bool is_complete = 128 download_item_->GetState() == DownloadItem::COMPLETE; 129 on_completed_loop_->PostTask(FROM_HERE, base::Bind( 130 on_completed_, is_complete)); 131 on_completed_.Reset(); 132 } 133 download_item_->RemoveObserver(this); 134 download_item_ = NULL; 135 } 136 137 base::MessageLoop* on_completed_loop_; 138 OnCompleted on_completed_; 139 GURL url_; 140 Referrer referrer_; 141 std::string referrer_encoding_; 142 WebContents* web_contents_; 143 DownloadItem* download_item_; 144 145 // Only used in the callback from DownloadManager::DownloadUrl(). 146 base::WeakPtrFactory<DragDownloadFileUI> weak_ptr_factory_; 147 148 DISALLOW_COPY_AND_ASSIGN(DragDownloadFileUI); 149 }; 150 151 DragDownloadFile::DragDownloadFile(const base::FilePath& file_path, 152 scoped_ptr<net::FileStream> file_stream, 153 const GURL& url, 154 const Referrer& referrer, 155 const std::string& referrer_encoding, 156 WebContents* web_contents) 157 : file_path_(file_path), 158 file_stream_(file_stream.Pass()), 159 drag_message_loop_(base::MessageLoop::current()), 160 state_(INITIALIZED), 161 drag_ui_(NULL), 162 weak_ptr_factory_(this) { 163 drag_ui_ = new DragDownloadFileUI( 164 url, 165 referrer, 166 referrer_encoding, 167 web_contents, 168 drag_message_loop_, 169 base::Bind(&DragDownloadFile::DownloadCompleted, 170 weak_ptr_factory_.GetWeakPtr())); 171 DCHECK(!file_path_.empty()); 172 } 173 174 DragDownloadFile::~DragDownloadFile() { 175 CheckThread(); 176 177 // This is the only place that drag_ui_ can be deleted from. Post a message to 178 // the UI thread so that it calls RemoveObserver on the right thread, and so 179 // that this task will run after the InitiateDownload task runs on the UI 180 // thread. 181 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 182 &DragDownloadFileUI::Delete, base::Unretained(drag_ui_))); 183 drag_ui_ = NULL; 184 } 185 186 void DragDownloadFile::Start(ui::DownloadFileObserver* observer) { 187 CheckThread(); 188 189 if (state_ != INITIALIZED) 190 return; 191 state_ = STARTED; 192 193 DCHECK(!observer_.get()); 194 observer_ = observer; 195 DCHECK(observer_.get()); 196 197 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 198 &DragDownloadFileUI::InitiateDownload, base::Unretained(drag_ui_), 199 base::Passed(&file_stream_), file_path_)); 200 } 201 202 bool DragDownloadFile::Wait() { 203 CheckThread(); 204 if (state_ == STARTED) 205 nested_loop_.Run(); 206 return state_ == SUCCESS; 207 } 208 209 void DragDownloadFile::Stop() { 210 CheckThread(); 211 if (drag_ui_) { 212 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 213 &DragDownloadFileUI::Cancel, base::Unretained(drag_ui_))); 214 } 215 } 216 217 void DragDownloadFile::DownloadCompleted(bool is_successful) { 218 CheckThread(); 219 220 state_ = is_successful ? SUCCESS : FAILURE; 221 222 if (is_successful) 223 observer_->OnDownloadCompleted(file_path_); 224 else 225 observer_->OnDownloadAborted(); 226 227 // Release the observer since we do not need it any more. 228 observer_ = NULL; 229 230 if (nested_loop_.running()) 231 nested_loop_.Quit(); 232 } 233 234 void DragDownloadFile::CheckThread() { 235 #if defined(OS_WIN) 236 DCHECK(drag_message_loop_ == base::MessageLoop::current()); 237 #else 238 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 239 #endif 240 } 241 242 } // namespace content 243