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
18namespace content {
19
20namespace {
21
22typedef 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.
33class 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
151DragDownloadFile::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
174DragDownloadFile::~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
186void 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
202bool DragDownloadFile::Wait() {
203  CheckThread();
204  if (state_ == STARTED)
205    nested_loop_.Run();
206  return state_ == SUCCESS;
207}
208
209void 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
217void 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
234void 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