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/files/file.h"
9 #include "base/message_loop/message_loop.h"
10 #include "content/browser/download/download_stats.h"
11 #include "content/browser/web_contents/web_contents_impl.h"
12 #include "content/public/browser/browser_context.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/download_item.h"
15 #include "content/public/browser/download_save_info.h"
16 #include "content/public/browser/download_url_parameters.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:
DragDownloadFileUI(const GURL & url,const Referrer & referrer,const std::string & referrer_encoding,WebContents * web_contents,base::MessageLoop * on_completed_loop,const OnCompleted & on_completed)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
InitiateDownload(base::File file,const base::FilePath & file_path)56 void InitiateDownload(base::File file,
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(file.Pass()); // Nulls file.
71 download_manager->DownloadUrl(params.Pass());
72 }
73
Cancel()74 void Cancel() {
75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
76 if (download_item_)
77 download_item_->Cancel(true);
78 }
79
Delete()80 void Delete() {
81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
82 delete this;
83 }
84
85 private:
~DragDownloadFileUI()86 virtual ~DragDownloadFileUI() {
87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
88 if (download_item_)
89 download_item_->RemoveObserver(this);
90 }
91
OnDownloadStarted(DownloadItem * item,DownloadInterruptReason interrupt_reason)92 void OnDownloadStarted(DownloadItem* item,
93 DownloadInterruptReason interrupt_reason) {
94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
95 if (!item) {
96 DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
97 on_completed_loop_->PostTask(FROM_HERE, base::Bind(on_completed_, false));
98 return;
99 }
100 DCHECK_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
101 download_item_ = item;
102 download_item_->AddObserver(this);
103 }
104
105 // DownloadItem::Observer:
OnDownloadUpdated(DownloadItem * item)106 virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE {
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108 DCHECK_EQ(download_item_, item);
109 DownloadItem::DownloadState state = download_item_->GetState();
110 if (state == DownloadItem::COMPLETE ||
111 state == DownloadItem::CANCELLED ||
112 state == DownloadItem::INTERRUPTED) {
113 if (!on_completed_.is_null()) {
114 on_completed_loop_->PostTask(FROM_HERE, base::Bind(
115 on_completed_, state == DownloadItem::COMPLETE));
116 on_completed_.Reset();
117 }
118 download_item_->RemoveObserver(this);
119 download_item_ = NULL;
120 }
121 // Ignore other states.
122 }
123
OnDownloadDestroyed(DownloadItem * item)124 virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {
125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
126 DCHECK_EQ(download_item_, item);
127 if (!on_completed_.is_null()) {
128 const bool is_complete =
129 download_item_->GetState() == DownloadItem::COMPLETE;
130 on_completed_loop_->PostTask(FROM_HERE, base::Bind(
131 on_completed_, is_complete));
132 on_completed_.Reset();
133 }
134 download_item_->RemoveObserver(this);
135 download_item_ = NULL;
136 }
137
138 base::MessageLoop* on_completed_loop_;
139 OnCompleted on_completed_;
140 GURL url_;
141 Referrer referrer_;
142 std::string referrer_encoding_;
143 WebContents* web_contents_;
144 DownloadItem* download_item_;
145
146 // Only used in the callback from DownloadManager::DownloadUrl().
147 base::WeakPtrFactory<DragDownloadFileUI> weak_ptr_factory_;
148
149 DISALLOW_COPY_AND_ASSIGN(DragDownloadFileUI);
150 };
151
DragDownloadFile(const base::FilePath & file_path,base::File file,const GURL & url,const Referrer & referrer,const std::string & referrer_encoding,WebContents * web_contents)152 DragDownloadFile::DragDownloadFile(const base::FilePath& file_path,
153 base::File file,
154 const GURL& url,
155 const Referrer& referrer,
156 const std::string& referrer_encoding,
157 WebContents* web_contents)
158 : file_path_(file_path),
159 file_(file.Pass()),
160 drag_message_loop_(base::MessageLoop::current()),
161 state_(INITIALIZED),
162 drag_ui_(NULL),
163 weak_ptr_factory_(this) {
164 drag_ui_ = new DragDownloadFileUI(
165 url,
166 referrer,
167 referrer_encoding,
168 web_contents,
169 drag_message_loop_,
170 base::Bind(&DragDownloadFile::DownloadCompleted,
171 weak_ptr_factory_.GetWeakPtr()));
172 DCHECK(!file_path_.empty());
173 }
174
~DragDownloadFile()175 DragDownloadFile::~DragDownloadFile() {
176 CheckThread();
177
178 // This is the only place that drag_ui_ can be deleted from. Post a message to
179 // the UI thread so that it calls RemoveObserver on the right thread, and so
180 // that this task will run after the InitiateDownload task runs on the UI
181 // thread.
182 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
183 &DragDownloadFileUI::Delete, base::Unretained(drag_ui_)));
184 drag_ui_ = NULL;
185 }
186
Start(ui::DownloadFileObserver * observer)187 void DragDownloadFile::Start(ui::DownloadFileObserver* observer) {
188 CheckThread();
189
190 if (state_ != INITIALIZED)
191 return;
192 state_ = STARTED;
193
194 DCHECK(!observer_.get());
195 observer_ = observer;
196 DCHECK(observer_.get());
197
198 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
199 &DragDownloadFileUI::InitiateDownload, base::Unretained(drag_ui_),
200 base::Passed(&file_), file_path_));
201 }
202
Wait()203 bool DragDownloadFile::Wait() {
204 CheckThread();
205 if (state_ == STARTED)
206 nested_loop_.Run();
207 return state_ == SUCCESS;
208 }
209
Stop()210 void DragDownloadFile::Stop() {
211 CheckThread();
212 if (drag_ui_) {
213 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
214 &DragDownloadFileUI::Cancel, base::Unretained(drag_ui_)));
215 }
216 }
217
DownloadCompleted(bool is_successful)218 void DragDownloadFile::DownloadCompleted(bool is_successful) {
219 CheckThread();
220
221 state_ = is_successful ? SUCCESS : FAILURE;
222
223 if (is_successful)
224 observer_->OnDownloadCompleted(file_path_);
225 else
226 observer_->OnDownloadAborted();
227
228 // Release the observer since we do not need it any more.
229 observer_ = NULL;
230
231 if (nested_loop_.running())
232 nested_loop_.Quit();
233 }
234
CheckThread()235 void DragDownloadFile::CheckThread() {
236 #if defined(OS_WIN)
237 DCHECK(drag_message_loop_ == base::MessageLoop::current());
238 #else
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240 #endif
241 }
242
243 } // namespace content
244