1// Copyright (c) 2011 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 "chrome/browser/download/drag_download_file.h"
6
7#include "base/file_util.h"
8#include "base/message_loop.h"
9#include "chrome/browser/download/download_file.h"
10#include "chrome/browser/download/download_item.h"
11#include "chrome/browser/download/download_util.h"
12#include "chrome/browser/profiles/profile.h"
13#include "content/browser/browser_thread.h"
14#include "content/browser/tab_contents/tab_contents.h"
15#include "net/base/file_stream.h"
16
17DragDownloadFile::DragDownloadFile(
18    const FilePath& file_name_or_path,
19    linked_ptr<net::FileStream> file_stream,
20    const GURL& url,
21    const GURL& referrer,
22    const std::string& referrer_encoding,
23    TabContents* tab_contents)
24    : file_stream_(file_stream),
25      url_(url),
26      referrer_(referrer),
27      referrer_encoding_(referrer_encoding),
28      tab_contents_(tab_contents),
29      drag_message_loop_(MessageLoop::current()),
30      is_started_(false),
31      is_successful_(false),
32      download_manager_(NULL),
33      download_item_observer_added_(false) {
34#if defined(OS_WIN)
35  DCHECK(!file_name_or_path.empty() && !file_stream.get());
36  file_name_ = file_name_or_path;
37#elif defined(OS_POSIX)
38  DCHECK(!file_name_or_path.empty() && file_stream.get());
39  file_path_ = file_name_or_path;
40#endif
41}
42
43DragDownloadFile::~DragDownloadFile() {
44  AssertCurrentlyOnDragThread();
45
46  // Since the target application can still hold and use the dragged file,
47  // we do not know the time that it can be safely deleted. To solve this
48  // problem, we schedule it to be removed after the system is restarted.
49#if defined(OS_WIN)
50  if (!temp_dir_path_.empty()) {
51    if (!file_path_.empty())
52      file_util::DeleteAfterReboot(file_path_);
53    file_util::DeleteAfterReboot(temp_dir_path_);
54  }
55#endif
56
57  if (download_manager_)
58    download_manager_->RemoveObserver(this);
59}
60
61bool DragDownloadFile::Start(ui::DownloadFileObserver* observer) {
62  AssertCurrentlyOnDragThread();
63
64  if (is_started_)
65    return true;
66  is_started_ = true;
67
68  DCHECK(!observer_.get());
69  observer_ = observer;
70
71  if (!file_stream_.get()) {
72    // Create a temporary directory to save the temporary download file. We do
73    // not want to use the default download directory since we do not want the
74    // twisted file name shown in the download shelf if the file with the same
75    // name already exists.
76    if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome"),
77                                           &temp_dir_path_))
78      return false;
79
80    file_path_ = temp_dir_path_.Append(file_name_);
81  }
82
83  InitiateDownload();
84
85  // On Windows, we need to wait till the download file is completed.
86#if defined(OS_WIN)
87  StartNestedMessageLoop();
88#endif
89
90  return is_successful_;
91}
92
93void DragDownloadFile::Stop() {
94}
95
96void DragDownloadFile::InitiateDownload() {
97#if defined(OS_WIN)
98  // DownloadManager could only be invoked from the UI thread.
99  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
100    BrowserThread::PostTask(
101        BrowserThread::UI, FROM_HERE,
102        NewRunnableMethod(this,
103                          &DragDownloadFile::InitiateDownload));
104    return;
105  }
106#endif
107
108  download_manager_ = tab_contents_->profile()->GetDownloadManager();
109  download_manager_->AddObserver(this);
110
111  DownloadSaveInfo save_info;
112  save_info.file_path = file_path_;
113  save_info.file_stream = file_stream_;
114  download_manager_->DownloadUrlToFile(url_,
115                                       referrer_,
116                                       referrer_encoding_,
117                                       save_info,
118                                       tab_contents_);
119  download_util::RecordDownloadCount(
120      download_util::INITIATED_BY_DRAG_N_DROP_COUNT);
121}
122
123void DragDownloadFile::DownloadCompleted(bool is_successful) {
124#if defined(OS_WIN)
125  // If not in drag-and-drop thread, defer the running to it.
126  if (drag_message_loop_ != MessageLoop::current()) {
127    drag_message_loop_->PostTask(
128        FROM_HERE,
129        NewRunnableMethod(this,
130                          &DragDownloadFile::DownloadCompleted,
131                          is_successful));
132    return;
133  }
134#endif
135
136  is_successful_ = is_successful;
137
138  // Call the observer.
139  DCHECK(observer_);
140  if (is_successful)
141    observer_->OnDownloadCompleted(file_path_);
142  else
143    observer_->OnDownloadAborted();
144
145  // Release the observer since we do not need it any more.
146  observer_ = NULL;
147
148  // On Windows, we need to stop the waiting.
149#if defined(OS_WIN)
150  QuitNestedMessageLoop();
151#endif
152}
153
154void DragDownloadFile::ModelChanged() {
155  AssertCurrentlyOnUIThread();
156
157  std::vector<DownloadItem*> downloads;
158  download_manager_->GetTemporaryDownloads(file_path_.DirName(), &downloads);
159  for (std::vector<DownloadItem*>::const_iterator i = downloads.begin();
160       i != downloads.end(); ++i) {
161    if (!download_item_observer_added_ && (*i)->original_url() == url_) {
162      download_item_observer_added_ = true;
163      (*i)->AddObserver(this);
164    }
165  }
166}
167
168void DragDownloadFile::OnDownloadUpdated(DownloadItem* download) {
169  AssertCurrentlyOnUIThread();
170  if (download->IsCancelled()) {
171    download->RemoveObserver(this);
172    download_manager_->RemoveObserver(this);
173
174    DownloadCompleted(false);
175  } else if (download->IsComplete()) {
176    download->RemoveObserver(this);
177    download_manager_->RemoveObserver(this);
178    DownloadCompleted(true);
179  }
180  // Ignore other states.
181}
182
183void DragDownloadFile::AssertCurrentlyOnDragThread() {
184  // Only do the check on Windows where two threads are involved.
185#if defined(OS_WIN)
186  DCHECK(drag_message_loop_ == MessageLoop::current());
187#endif
188}
189
190void DragDownloadFile::AssertCurrentlyOnUIThread() {
191  // Only do the check on Windows where two threads are involved.
192#if defined(OS_WIN)
193  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194#endif
195}
196
197#if defined(OS_WIN)
198void DragDownloadFile::StartNestedMessageLoop() {
199  AssertCurrentlyOnDragThread();
200
201  bool old_state = MessageLoop::current()->NestableTasksAllowed();
202  MessageLoop::current()->SetNestableTasksAllowed(true);
203  is_running_nested_message_loop_ = true;
204  MessageLoop::current()->Run();
205  MessageLoop::current()->SetNestableTasksAllowed(old_state);
206}
207
208void DragDownloadFile::QuitNestedMessageLoop() {
209  AssertCurrentlyOnDragThread();
210
211  if (is_running_nested_message_loop_) {
212    is_running_nested_message_loop_ = false;
213    MessageLoop::current()->Quit();
214  }
215}
216#endif
217