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