download_file_manager.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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/download_file_manager.h"
6
7#include "base/file_util.h"
8#include "base/stl_util-inl.h"
9#include "base/task.h"
10#include "base/utf_string_conversions.h"
11#include "build/build_config.h"
12#include "chrome/browser/browser_thread.h"
13#include "chrome/browser/download/download_manager.h"
14#include "chrome/browser/download/download_util.h"
15#include "chrome/browser/history/download_create_info.h"
16#include "chrome/browser/net/chrome_url_request_context.h"
17#include "chrome/browser/platform_util.h"
18#include "chrome/browser/profile.h"
19#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
20#include "chrome/browser/tab_contents/tab_util.h"
21#include "chrome/browser/tab_contents/tab_contents.h"
22#include "googleurl/src/gurl.h"
23#include "net/base/io_buffer.h"
24
25#if defined(OS_WIN)
26#include "app/win_util.h"
27#include "chrome/common/win_safe_util.h"
28#elif defined(OS_MACOSX)
29#include "chrome/browser/cocoa/file_metadata.h"
30#endif
31
32namespace {
33
34// Throttle updates to the UI thread so that a fast moving download doesn't
35// cause it to become unresponsive (in milliseconds).
36const int kUpdatePeriodMs = 500;
37
38DownloadManager* DownloadManagerForRenderViewHost(int render_process_id,
39                                                  int render_view_id) {
40  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
41
42  TabContents* contents = tab_util::GetTabContentsByID(render_process_id,
43                                                       render_view_id);
44  if (contents) {
45    Profile* profile = contents->profile();
46    if (profile)
47      return profile->GetDownloadManager();
48  }
49
50  return NULL;
51}
52
53}  // namespace
54
55DownloadFileManager::DownloadFileManager(ResourceDispatcherHost* rdh)
56    : next_id_(0),
57      resource_dispatcher_host_(rdh) {
58}
59
60DownloadFileManager::~DownloadFileManager() {
61  DCHECK(downloads_.empty());
62}
63
64void DownloadFileManager::Shutdown() {
65  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
66  BrowserThread::PostTask(
67      BrowserThread::FILE, FROM_HERE,
68      NewRunnableMethod(this, &DownloadFileManager::OnShutdown));
69}
70
71void DownloadFileManager::OnShutdown() {
72  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
73  StopUpdateTimer();
74  STLDeleteValues(&downloads_);
75}
76
77void DownloadFileManager::CreateDownloadFile(
78    DownloadCreateInfo* info, DownloadManager* download_manager) {
79  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
80
81  scoped_ptr<DownloadFile> download_file(
82      new DownloadFile(info, download_manager));
83  if (!download_file->Initialize()) {
84    BrowserThread::PostTask(
85        BrowserThread::IO, FROM_HERE,
86        NewRunnableFunction(&download_util::CancelDownloadRequest,
87                            resource_dispatcher_host_,
88                            info->child_id,
89                            info->request_id));
90    delete info;
91    return;
92  }
93
94  DCHECK(GetDownloadFile(info->download_id) == NULL);
95  downloads_[info->download_id] = download_file.release();
96  // TODO(phajdan.jr): fix the duplication of path info below.
97  info->path = info->save_info.file_path;
98
99  // The file is now ready, we can un-pause the request and start saving data.
100  BrowserThread::PostTask(
101      BrowserThread::IO, FROM_HERE,
102      NewRunnableMethod(this, &DownloadFileManager::ResumeDownloadRequest,
103                        info->child_id, info->request_id));
104
105  StartUpdateTimer();
106
107  BrowserThread::PostTask(
108      BrowserThread::UI, FROM_HERE,
109      NewRunnableMethod(download_manager,
110                        &DownloadManager::StartDownload, info));
111}
112
113void DownloadFileManager::ResumeDownloadRequest(int child_id, int request_id) {
114  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
115
116  // This balances the pause in DownloadResourceHandler::OnResponseStarted.
117  resource_dispatcher_host_->PauseRequest(child_id, request_id, false);
118}
119
120DownloadFile* DownloadFileManager::GetDownloadFile(int id) {
121  DownloadFileMap::iterator it = downloads_.find(id);
122  return it == downloads_.end() ? NULL : it->second;
123}
124
125void DownloadFileManager::StartUpdateTimer() {
126  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
127  if (!update_timer_.IsRunning()) {
128    update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdatePeriodMs),
129                        this, &DownloadFileManager::UpdateInProgressDownloads);
130  }
131}
132
133void DownloadFileManager::StopUpdateTimer() {
134  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
135  update_timer_.Stop();
136}
137
138void DownloadFileManager::UpdateInProgressDownloads() {
139  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
140  for (DownloadFileMap::iterator i = downloads_.begin();
141       i != downloads_.end(); ++i) {
142    int id = i->first;
143    DownloadFile* download_file = i->second;
144    DownloadManager* manager = download_file->GetDownloadManager();
145    if (manager) {
146      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
147          NewRunnableMethod(manager, &DownloadManager::UpdateDownload,
148                            id, download_file->bytes_so_far()));
149    }
150  }
151}
152
153// Called on the IO thread once the ResourceDispatcherHost has decided that a
154// request is a download.
155int DownloadFileManager::GetNextId() {
156  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
157  return next_id_++;
158}
159
160void DownloadFileManager::StartDownload(DownloadCreateInfo* info) {
161  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
162  DCHECK(info);
163
164  DownloadManager* manager = DownloadManagerForRenderViewHost(
165      info->child_id, info->render_view_id);
166  if (!manager) {
167    BrowserThread::PostTask(
168        BrowserThread::IO, FROM_HERE,
169        NewRunnableFunction(&download_util::CancelDownloadRequest,
170                            resource_dispatcher_host_,
171                            info->child_id,
172                            info->request_id));
173    delete info;
174    return;
175  }
176
177  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
178      NewRunnableMethod(this, &DownloadFileManager::CreateDownloadFile,
179                        info, make_scoped_refptr(manager)));
180}
181
182// We don't forward an update to the UI thread here, since we want to throttle
183// the UI update rate via a periodic timer. If the user has cancelled the
184// download (in the UI thread), we may receive a few more updates before the IO
185// thread gets the cancel message: we just delete the data since the
186// DownloadFile has been deleted.
187void DownloadFileManager::UpdateDownload(int id, DownloadBuffer* buffer) {
188  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
189  std::vector<DownloadBuffer::Contents> contents;
190  {
191    AutoLock auto_lock(buffer->lock);
192    contents.swap(buffer->contents);
193  }
194
195  DownloadFile* download = GetDownloadFile(id);
196  for (size_t i = 0; i < contents.size(); ++i) {
197    net::IOBuffer* data = contents[i].first;
198    const int data_len = contents[i].second;
199    if (download)
200      download->AppendDataToFile(data->data(), data_len);
201    data->Release();
202  }
203}
204
205void DownloadFileManager::OnResponseCompleted(int id, DownloadBuffer* buffer) {
206  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
207  delete buffer;
208  DownloadFileMap::iterator it = downloads_.find(id);
209  if (it != downloads_.end()) {
210    DownloadFile* download = it->second;
211    download->Finish();
212
213    DownloadManager* download_manager = download->GetDownloadManager();
214    if (download_manager) {
215      BrowserThread::PostTask(
216          BrowserThread::UI, FROM_HERE,
217          NewRunnableMethod(
218              download_manager, &DownloadManager::OnAllDataSaved,
219              id, download->bytes_so_far()));
220    }
221
222    // We need to keep the download around until the UI thread has finalized
223    // the name.
224    if (download->path_renamed()) {
225      downloads_.erase(it);
226      delete download;
227    }
228  }
229
230  if (downloads_.empty())
231    StopUpdateTimer();
232}
233
234// This method will be sent via a user action, or shutdown on the UI thread, and
235// run on the download thread. Since this message has been sent from the UI
236// thread, the download may have already completed and won't exist in our map.
237void DownloadFileManager::CancelDownload(int id) {
238  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
239  DownloadFileMap::iterator it = downloads_.find(id);
240  if (it != downloads_.end()) {
241    DownloadFile* download = it->second;
242    download->Cancel();
243
244    if (download->path_renamed()) {
245      downloads_.erase(it);
246      delete download;
247    }
248  }
249
250  if (downloads_.empty())
251    StopUpdateTimer();
252}
253
254void DownloadFileManager::OnDownloadManagerShutdown(DownloadManager* manager) {
255  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
256  DCHECK(manager);
257
258  std::set<DownloadFile*> to_remove;
259
260  for (DownloadFileMap::iterator i = downloads_.begin();
261       i != downloads_.end(); ++i) {
262    DownloadFile* download_file = i->second;
263    if (download_file->GetDownloadManager() == manager) {
264      download_file->CancelDownloadRequest(resource_dispatcher_host_);
265      to_remove.insert(download_file);
266    }
267  }
268
269  for (std::set<DownloadFile*>::iterator i = to_remove.begin();
270       i != to_remove.end(); ++i) {
271    downloads_.erase((*i)->id());
272    delete *i;
273  }
274}
275
276// Actions from the UI thread and run on the download thread
277
278// The DownloadManager in the UI thread has provided an intermediate .crdownload
279// name for the download specified by 'id'. Rename the in progress download.
280void DownloadFileManager::OnIntermediateDownloadName(
281    int id, const FilePath& full_path, DownloadManager* download_manager)
282{
283  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
284  DownloadFileMap::iterator it = downloads_.find(id);
285  if (it == downloads_.end())
286    return;
287
288  DownloadFile* download = it->second;
289  if (!download->Rename(full_path, false /* is_final_rename */)) {
290    // Error. Between the time the UI thread generated 'full_path' to the time
291    // this code runs, something happened that prevents us from renaming.
292    CancelDownloadOnRename(id);
293  }
294}
295
296// The DownloadManager in the UI thread has provided a final name for the
297// download specified by 'id'. Rename the in progress download, and remove it
298// from our table if it has been completed or cancelled already.
299// |need_delete_crdownload| indicates if we explicitly delete an intermediate
300// .crdownload file or not.
301//
302// There are 3 possible rename cases where this method can be called:
303// 1. tmp -> foo            (need_delete_crdownload=T)
304// 2. foo.crdownload -> foo (need_delete_crdownload=F)
305// 3. tmp-> unconfirmed.xxx.crdownload (need_delete_crdownload=F)
306void DownloadFileManager::OnFinalDownloadName(
307    int id, const FilePath& full_path, bool need_delete_crdownload,
308    DownloadManager* download_manager) {
309  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
310
311  DownloadFile* download = GetDownloadFile(id);
312  if (!download)
313    return;
314
315  if (download->Rename(full_path, true /* is_final_rename */)) {
316#if defined(OS_MACOSX)
317    // Done here because we only want to do this once; see
318    // http://crbug.com/13120 for details.
319    download->AnnotateWithSourceInformation();
320#endif
321    BrowserThread::PostTask(
322        BrowserThread::UI, FROM_HERE,
323        NewRunnableMethod(
324            download_manager, &DownloadManager::DownloadRenamedToFinalName, id,
325            full_path));
326  } else {
327    // Error. Between the time the UI thread generated 'full_path' to the time
328    // this code runs, something happened that prevents us from renaming.
329    CancelDownloadOnRename(id);
330  }
331
332  if (need_delete_crdownload)
333    download->DeleteCrDownload();
334
335  // If the download has completed before we got this final name, we remove it
336  // from our in progress map.
337  if (!download->in_progress()) {
338    downloads_.erase(id);
339    delete download;
340  }
341
342  if (downloads_.empty())
343    StopUpdateTimer();
344}
345
346// Called only from OnFinalDownloadName or OnIntermediateDownloadName
347// on the FILE thread.
348void DownloadFileManager::CancelDownloadOnRename(int id) {
349  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
350
351  DownloadFile* download = GetDownloadFile(id);
352  if (!download)
353    return;
354
355  DownloadManager* download_manager = download->GetDownloadManager();
356  if (!download_manager) {
357    download->CancelDownloadRequest(resource_dispatcher_host_);
358    return;
359  }
360
361  BrowserThread::PostTask(
362      BrowserThread::UI, FROM_HERE,
363      NewRunnableMethod(download_manager,
364                        &DownloadManager::DownloadCancelled, id));
365}
366