// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/download/download_file_manager.h" #include "base/file_util.h" #include "base/logging.h" #include "base/stl_util-inl.h" #include "base/task.h" #include "base/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_util.h" #include "chrome/browser/history/download_create_info.h" #include "chrome/browser/net/chrome_url_request_context.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_host/resource_dispatcher_host.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "googleurl/src/gurl.h" #include "net/base/io_buffer.h" #if defined(OS_WIN) #include "app/win_util.h" #include "chrome/common/win_safe_util.h" #elif defined(OS_MACOSX) #include "chrome/browser/ui/cocoa/file_metadata.h" #endif namespace { // Throttle updates to the UI thread so that a fast moving download doesn't // cause it to become unresponsive (in milliseconds). const int kUpdatePeriodMs = 500; DownloadManager* DownloadManagerForRenderViewHost(int render_process_id, int render_view_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); TabContents* contents = tab_util::GetTabContentsByID(render_process_id, render_view_id); if (contents) { Profile* profile = contents->profile(); if (profile) return profile->GetDownloadManager(); } return NULL; } } // namespace DownloadFileManager::DownloadFileManager(ResourceDispatcherHost* rdh) : next_id_(0), resource_dispatcher_host_(rdh) { } DownloadFileManager::~DownloadFileManager() { DCHECK(downloads_.empty()); } void DownloadFileManager::Shutdown() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &DownloadFileManager::OnShutdown)); } void DownloadFileManager::OnShutdown() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); StopUpdateTimer(); STLDeleteValues(&downloads_); } void DownloadFileManager::CreateDownloadFile( DownloadCreateInfo* info, DownloadManager* download_manager) { VLOG(20) << __FUNCTION__ << "()" << " info = " << info->DebugString(); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); scoped_ptr download_file( new DownloadFile(info, download_manager)); if (!download_file->Initialize()) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, NewRunnableFunction(&download_util::CancelDownloadRequest, resource_dispatcher_host_, info->child_id, info->request_id)); delete info; return; } DCHECK(GetDownloadFile(info->download_id) == NULL); downloads_[info->download_id] = download_file.release(); // TODO(phajdan.jr): fix the duplication of path info below. info->path = info->save_info.file_path; // The file is now ready, we can un-pause the request and start saving data. BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, NewRunnableMethod(this, &DownloadFileManager::ResumeDownloadRequest, info->child_id, info->request_id)); StartUpdateTimer(); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(download_manager, &DownloadManager::StartDownload, info)); } void DownloadFileManager::ResumeDownloadRequest(int child_id, int request_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // This balances the pause in DownloadResourceHandler::OnResponseStarted. resource_dispatcher_host_->PauseRequest(child_id, request_id, false); } DownloadFile* DownloadFileManager::GetDownloadFile(int id) { DownloadFileMap::iterator it = downloads_.find(id); return it == downloads_.end() ? NULL : it->second; } void DownloadFileManager::StartUpdateTimer() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (!update_timer_.IsRunning()) { update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdatePeriodMs), this, &DownloadFileManager::UpdateInProgressDownloads); } } void DownloadFileManager::StopUpdateTimer() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); update_timer_.Stop(); } void DownloadFileManager::UpdateInProgressDownloads() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); for (DownloadFileMap::iterator i = downloads_.begin(); i != downloads_.end(); ++i) { int id = i->first; DownloadFile* download_file = i->second; DownloadManager* manager = download_file->GetDownloadManager(); if (manager) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(manager, &DownloadManager::UpdateDownload, id, download_file->bytes_so_far())); } } } // Called on the IO thread once the ResourceDispatcherHost has decided that a // request is a download. int DownloadFileManager::GetNextId() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); return next_id_++; } void DownloadFileManager::StartDownload(DownloadCreateInfo* info) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(info); DownloadManager* manager = DownloadManagerForRenderViewHost( info->child_id, info->render_view_id); if (!manager) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, NewRunnableFunction(&download_util::CancelDownloadRequest, resource_dispatcher_host_, info->child_id, info->request_id)); delete info; return; } BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &DownloadFileManager::CreateDownloadFile, info, make_scoped_refptr(manager))); } // We don't forward an update to the UI thread here, since we want to throttle // the UI update rate via a periodic timer. If the user has cancelled the // download (in the UI thread), we may receive a few more updates before the IO // thread gets the cancel message: we just delete the data since the // DownloadFile has been deleted. void DownloadFileManager::UpdateDownload(int id, DownloadBuffer* buffer) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::vector contents; { AutoLock auto_lock(buffer->lock); contents.swap(buffer->contents); } DownloadFile* download = GetDownloadFile(id); for (size_t i = 0; i < contents.size(); ++i) { net::IOBuffer* data = contents[i].first; const int data_len = contents[i].second; if (download) download->AppendDataToFile(data->data(), data_len); data->Release(); } } void DownloadFileManager::OnResponseCompleted(int id, DownloadBuffer* buffer) { VLOG(20) << __FUNCTION__ << "()" << " id = " << id; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); delete buffer; DownloadFileMap::iterator it = downloads_.find(id); if (it != downloads_.end()) { DownloadFile* download = it->second; download->Finish(); DownloadManager* download_manager = download->GetDownloadManager(); if (download_manager) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod( download_manager, &DownloadManager::OnAllDataSaved, id, download->bytes_so_far())); } // We need to keep the download around until the UI thread has finalized // the name. if (download->path_renamed()) { downloads_.erase(it); delete download; } } if (downloads_.empty()) StopUpdateTimer(); } // This method will be sent via a user action, or shutdown on the UI thread, and // run on the download thread. Since this message has been sent from the UI // thread, the download may have already completed and won't exist in our map. void DownloadFileManager::CancelDownload(int id) { VLOG(20) << __FUNCTION__ << "()" << " id = " << id; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFileMap::iterator it = downloads_.find(id); if (it != downloads_.end()) { DownloadFile* download = it->second; VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(); download->Cancel(); if (download->path_renamed()) { downloads_.erase(it); delete download; } } if (downloads_.empty()) StopUpdateTimer(); } void DownloadFileManager::OnDownloadManagerShutdown(DownloadManager* manager) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(manager); std::set to_remove; for (DownloadFileMap::iterator i = downloads_.begin(); i != downloads_.end(); ++i) { DownloadFile* download_file = i->second; if (download_file->GetDownloadManager() == manager) { download_file->CancelDownloadRequest(resource_dispatcher_host_); to_remove.insert(download_file); } } for (std::set::iterator i = to_remove.begin(); i != to_remove.end(); ++i) { downloads_.erase((*i)->id()); delete *i; } } // Actions from the UI thread and run on the download thread // The DownloadManager in the UI thread has provided an intermediate .crdownload // name for the download specified by 'id'. Rename the in progress download. void DownloadFileManager::OnIntermediateDownloadName( int id, const FilePath& full_path, DownloadManager* download_manager) { VLOG(20) << __FUNCTION__ << "()" << " id = " << id << " full_path = \"" << full_path.value() << "\""; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFileMap::iterator it = downloads_.find(id); if (it == downloads_.end()) return; DownloadFile* download = it->second; VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(); if (!download->Rename(full_path, false /* is_final_rename */)) { // Error. Between the time the UI thread generated 'full_path' to the time // this code runs, something happened that prevents us from renaming. CancelDownloadOnRename(id); } } // The DownloadManager in the UI thread has provided a final name for the // download specified by 'id'. Rename the in progress download, and remove it // from our table if it has been completed or cancelled already. // |need_delete_crdownload| indicates if we explicitly delete an intermediate // .crdownload file or not. // // There are 3 possible rename cases where this method can be called: // 1. tmp -> foo (need_delete_crdownload=T) // 2. foo.crdownload -> foo (need_delete_crdownload=F) // 3. tmp-> unconfirmed.xxx.crdownload (need_delete_crdownload=F) void DownloadFileManager::OnFinalDownloadName( int id, const FilePath& full_path, bool need_delete_crdownload, DownloadManager* download_manager) { VLOG(20) << __FUNCTION__ << "()" << " id = " << id << " full_path = \"" << full_path.value() << "\"" << " need_delete_crdownload = " << need_delete_crdownload; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFile* download = GetDownloadFile(id); if (!download) return; VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(); if (download->Rename(full_path, true /* is_final_rename */)) { #if defined(OS_MACOSX) // Done here because we only want to do this once; see // http://crbug.com/13120 for details. download->AnnotateWithSourceInformation(); #endif BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod( download_manager, &DownloadManager::DownloadRenamedToFinalName, id, full_path)); } else { // Error. Between the time the UI thread generated 'full_path' to the time // this code runs, something happened that prevents us from renaming. CancelDownloadOnRename(id); } if (need_delete_crdownload) download->DeleteCrDownload(); // If the download has completed before we got this final name, we remove it // from our in progress map. if (!download->in_progress()) { downloads_.erase(id); delete download; } if (downloads_.empty()) StopUpdateTimer(); } // Called only from OnFinalDownloadName or OnIntermediateDownloadName // on the FILE thread. void DownloadFileManager::CancelDownloadOnRename(int id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFile* download = GetDownloadFile(id); if (!download) return; DownloadManager* download_manager = download->GetDownloadManager(); if (!download_manager) { download->CancelDownloadRequest(resource_dispatcher_host_); return; } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(download_manager, &DownloadManager::DownloadCancelled, id)); }