| // Copyright (c) 2006-2008 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.h" |
| |
| #include "base/file_util.h" |
| #include "base/path_service.h" |
| #include "base/stl_util-inl.h" |
| #include "base/task.h" |
| #include "base/thread.h" |
| #include "base/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/chrome_thread.h" |
| #include "chrome/browser/download/download_manager.h" |
| #include "chrome/browser/download/download_util.h" |
| #include "chrome/browser/net/chrome_url_request_context.h" |
| #include "chrome/browser/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 "chrome/common/chrome_paths.h" |
| #include "chrome/common/platform_util.h" |
| #include "googleurl/src/gurl.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_util.h" |
| #include "net/url_request/url_request_context.h" |
| |
| #if defined(OS_WIN) |
| #include "app/win_util.h" |
| #include "chrome/common/win_safe_util.h" |
| #elif defined(OS_MACOSX) |
| #include "chrome/browser/cocoa/file_metadata.h" |
| #endif |
| |
| // Throttle updates to the UI thread so that a fast moving download doesn't |
| // cause it to become unresponsive (in milliseconds). |
| static const int kUpdatePeriodMs = 500; |
| |
| // Timer task for posting UI updates. This task is created and maintained by |
| // the DownloadFileManager long as there is an in progress download. The task |
| // is cancelled when all active downloads have completed, or in the destructor |
| // of the DownloadFileManager. |
| class DownloadFileUpdateTask : public Task { |
| public: |
| explicit DownloadFileUpdateTask(DownloadFileManager* manager) |
| : manager_(manager) {} |
| virtual void Run() { |
| manager_->UpdateInProgressDownloads(); |
| } |
| |
| private: |
| DownloadFileManager* manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DownloadFileUpdateTask); |
| }; |
| |
| // DownloadFile implementation ------------------------------------------------- |
| |
| DownloadFile::DownloadFile(const DownloadCreateInfo* info) |
| : file_stream_(info->save_info.file_stream), |
| source_url_(info->url), |
| referrer_url_(info->referrer_url), |
| id_(info->download_id), |
| child_id_(info->child_id), |
| render_view_id_(info->render_view_id), |
| request_id_(info->request_id), |
| bytes_so_far_(0), |
| full_path_(info->save_info.file_path), |
| path_renamed_(false), |
| in_progress_(true), |
| dont_sleep_(true), |
| save_info_(info->save_info) { |
| } |
| |
| DownloadFile::~DownloadFile() { |
| Close(); |
| } |
| |
| bool DownloadFile::Initialize() { |
| if (!full_path_.empty() || |
| download_util::CreateTemporaryFileForDownload(&full_path_)) |
| return Open(); |
| return false; |
| } |
| |
| bool DownloadFile::AppendDataToFile(const char* data, int data_len) { |
| if (file_stream_.get()) { |
| // FIXME bug 595247: handle errors on file writes. |
| size_t written = file_stream_->Write(data, data_len, NULL); |
| bytes_so_far_ += written; |
| return true; |
| } |
| return false; |
| } |
| |
| void DownloadFile::Cancel() { |
| Close(); |
| if (!full_path_.empty()) |
| file_util::Delete(full_path_, false); |
| } |
| |
| // The UI has provided us with our finalized name. |
| bool DownloadFile::Rename(const FilePath& new_path) { |
| // If the new path is same as the old one, there is no need to perform the |
| // following renaming logic. |
| if (new_path == full_path_) { |
| path_renamed_ = true; |
| |
| // Don't close the file if we're not done (finished or canceled). |
| if (!in_progress_) |
| Close(); |
| |
| return true; |
| } |
| |
| Close(); |
| |
| #if defined(OS_WIN) |
| // We cannot rename because rename will keep the same security descriptor |
| // on the destination file. We want to recreate the security descriptor |
| // with the security that makes sense in the new path. |
| if (!file_util::RenameFileAndResetSecurityDescriptor(full_path_, new_path)) |
| return false; |
| #elif defined(OS_POSIX) |
| { |
| // Similarly, on Unix, we're moving a temp file created with permissions |
| // 600 to |new_path|. Here, we try to fix up the destination file with |
| // appropriate permissions. |
| struct stat st; |
| // First check the file existence and create an empty file if it doesn't |
| // exist. |
| if (!file_util::PathExists(new_path)) |
| file_util::WriteFile(new_path, "", 0); |
| bool stat_succeeded = (stat(new_path.value().c_str(), &st) == 0); |
| |
| // TODO(estade): Move() falls back to copying and deleting when a simple |
| // rename fails. Copying sucks for large downloads. crbug.com/8737 |
| if (!file_util::Move(full_path_, new_path)) |
| return false; |
| |
| if (stat_succeeded) |
| chmod(new_path.value().c_str(), st.st_mode); |
| } |
| #endif |
| |
| full_path_ = new_path; |
| path_renamed_ = true; |
| |
| // We don't need to re-open the file if we're done (finished or canceled). |
| if (!in_progress_) |
| return true; |
| |
| if (!Open()) |
| return false; |
| |
| // Move to the end of the new file. |
| if (file_stream_->Seek(net::FROM_END, 0) < 0) |
| return false; |
| |
| return true; |
| } |
| |
| void DownloadFile::Close() { |
| if (file_stream_.get()) { |
| file_stream_->Close(); |
| file_stream_.reset(); |
| } |
| } |
| |
| bool DownloadFile::Open() { |
| DCHECK(!full_path_.empty()); |
| |
| // Create a new file steram if it is not provided. |
| if (!file_stream_.get()) { |
| file_stream_.reset(new net::FileStream); |
| if (file_stream_->Open(full_path_, |
| base::PLATFORM_FILE_OPEN_ALWAYS | |
| base::PLATFORM_FILE_WRITE) != net::OK) { |
| file_stream_.reset(); |
| return false; |
| } |
| } |
| |
| #if defined(OS_WIN) |
| AnnotateWithSourceInformation(); |
| #endif |
| return true; |
| } |
| |
| void DownloadFile::AnnotateWithSourceInformation() { |
| #if defined(OS_WIN) |
| // Sets the Zone to tell Windows that this file comes from the internet. |
| // We ignore the return value because a failure is not fatal. |
| win_util::SetInternetZoneIdentifier(full_path_); |
| #elif defined(OS_MACOSX) |
| file_metadata::AddQuarantineMetadataToFile(full_path_, source_url_, |
| referrer_url_); |
| file_metadata::AddOriginMetadataToFile(full_path_, source_url_, |
| referrer_url_); |
| #endif |
| } |
| |
| // DownloadFileManager implementation ------------------------------------------ |
| |
| DownloadFileManager::DownloadFileManager(ResourceDispatcherHost* rdh) |
| : next_id_(0), |
| resource_dispatcher_host_(rdh) { |
| } |
| |
| DownloadFileManager::~DownloadFileManager() { |
| // Check for clean shutdown. |
| DCHECK(downloads_.empty()); |
| ui_progress_.clear(); |
| } |
| |
| // Called during the browser shutdown process to clean up any state (open files, |
| // timers) that live on the download_thread_. |
| void DownloadFileManager::Shutdown() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| StopUpdateTimer(); |
| ChromeThread::PostTask( |
| ChromeThread::FILE, FROM_HERE, |
| NewRunnableMethod(this, &DownloadFileManager::OnShutdown)); |
| } |
| |
| // Cease download thread operations. |
| void DownloadFileManager::OnShutdown() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| // Delete any partial downloads during shutdown. |
| for (DownloadFileMap::iterator it = downloads_.begin(); |
| it != downloads_.end(); ++it) { |
| DownloadFile* download = it->second; |
| if (download->in_progress()) |
| download->Cancel(); |
| delete download; |
| } |
| downloads_.clear(); |
| } |
| |
| // Lookup one in-progress download. |
| DownloadFile* DownloadFileManager::LookupDownload(int id) { |
| DownloadFileMap::iterator it = downloads_.find(id); |
| return it == downloads_.end() ? NULL : it->second; |
| } |
| |
| // The UI progress is updated on the file thread and removed on the UI thread. |
| void DownloadFileManager::RemoveDownloadFromUIProgress(int id) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| AutoLock lock(progress_lock_); |
| if (ui_progress_.find(id) != ui_progress_.end()) |
| ui_progress_.erase(id); |
| } |
| |
| // Throttle updates to the UI thread by only posting update notifications at a |
| // regularly controlled interval. |
| void DownloadFileManager::StartUpdateTimer() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| if (!update_timer_.IsRunning()) { |
| update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdatePeriodMs), |
| this, &DownloadFileManager::UpdateInProgressDownloads); |
| } |
| } |
| |
| void DownloadFileManager::StopUpdateTimer() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| update_timer_.Stop(); |
| } |
| |
| // Called on the IO thread once the ResourceDispatcherHost has decided that a |
| // request is a download. |
| int DownloadFileManager::GetNextId() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
| return next_id_++; |
| } |
| |
| // Notifications sent from the IO thread and run on the download thread: |
| |
| // The IO thread created 'info', but the download thread (this method) uses it |
| // to create a DownloadFile, then passes 'info' to the UI thread where it is |
| // finally consumed and deleted. |
| void DownloadFileManager::StartDownload(DownloadCreateInfo* info) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| DCHECK(info); |
| |
| DownloadFile* download = new DownloadFile(info); |
| if (!download->Initialize()) { |
| // Couldn't open, cancel the operation. The UI thread does not yet know |
| // about this download so we have to clean up 'info'. We need to get back |
| // to the IO thread to cancel the network request and CancelDownloadRequest |
| // on the UI thread is the safe way to do that. |
| ChromeThread::PostTask( |
| ChromeThread::IO, FROM_HERE, |
| NewRunnableFunction(&DownloadManager::OnCancelDownloadRequest, |
| resource_dispatcher_host_, |
| info->child_id, |
| info->request_id)); |
| delete info; |
| delete download; |
| return; |
| } |
| |
| DCHECK(LookupDownload(info->download_id) == NULL); |
| downloads_[info->download_id] = download; |
| info->path = download->full_path(); |
| { |
| AutoLock lock(progress_lock_); |
| ui_progress_[info->download_id] = info->received_bytes; |
| } |
| |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod(this, &DownloadFileManager::OnStartDownload, info)); |
| } |
| |
| // 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(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| std::vector<DownloadBuffer::Contents> contents; |
| { |
| AutoLock auto_lock(buffer->lock); |
| contents.swap(buffer->contents); |
| } |
| |
| DownloadFile* download = LookupDownload(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(); |
| } |
| |
| if (download) { |
| AutoLock lock(progress_lock_); |
| ui_progress_[download->id()] = download->bytes_so_far(); |
| } |
| } |
| |
| void DownloadFileManager::DownloadFinished(int id, DownloadBuffer* buffer) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| delete buffer; |
| DownloadFileMap::iterator it = downloads_.find(id); |
| if (it != downloads_.end()) { |
| DownloadFile* download = it->second; |
| download->set_in_progress(false); |
| |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod( |
| this, &DownloadFileManager::OnDownloadFinished, |
| 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()) |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod(this, &DownloadFileManager::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) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| DownloadFileMap::iterator it = downloads_.find(id); |
| if (it != downloads_.end()) { |
| DownloadFile* download = it->second; |
| download->set_in_progress(false); |
| |
| download->Cancel(); |
| |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod( |
| this, &DownloadFileManager::RemoveDownloadFromUIProgress, |
| download->id())); |
| |
| if (download->path_renamed()) { |
| downloads_.erase(it); |
| delete download; |
| } |
| } |
| |
| if (downloads_.empty()) { |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod(this, &DownloadFileManager::StopUpdateTimer)); |
| } |
| } |
| |
| // Our periodic timer has fired so send the UI thread updates on all in progress |
| // downloads. |
| void DownloadFileManager::UpdateInProgressDownloads() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| AutoLock lock(progress_lock_); |
| ProgressMap::iterator it = ui_progress_.begin(); |
| for (; it != ui_progress_.end(); ++it) { |
| const int id = it->first; |
| DownloadManager* manager = LookupManager(id); |
| if (manager) |
| manager->UpdateDownload(id, it->second); |
| } |
| } |
| |
| // Notifications sent from the download thread and run on the UI thread. |
| |
| // Lookup the DownloadManager for this TabContents' profile and inform it of |
| // a new download. |
| // TODO(paulg): When implementing download restart via the Downloads tab, |
| // there will be no 'render_process_id' or 'render_view_id'. |
| void DownloadFileManager::OnStartDownload(DownloadCreateInfo* info) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| DownloadManager* manager = DownloadManagerFromRenderIds(info->child_id, |
| info->render_view_id); |
| if (!manager) { |
| ChromeThread::PostTask( |
| ChromeThread::IO, FROM_HERE, |
| NewRunnableFunction(&DownloadManager::OnCancelDownloadRequest, |
| resource_dispatcher_host_, |
| info->child_id, |
| info->request_id)); |
| delete info; |
| return; |
| } |
| |
| StartUpdateTimer(); |
| |
| // Add the download manager to our request maps for future updates. We want to |
| // be able to cancel all in progress downloads when a DownloadManager is |
| // deleted, such as when a profile is closed. We also want to be able to look |
| // up the DownloadManager associated with a given request without having to |
| // rely on using tab information, since a tab may be closed while a download |
| // initiated from that tab is still in progress. |
| DownloadRequests& downloads = requests_[manager]; |
| downloads.insert(info->download_id); |
| |
| // TODO(paulg): The manager will exist when restarts are implemented. |
| DownloadManagerMap::iterator dit = managers_.find(info->download_id); |
| if (dit == managers_.end()) |
| managers_[info->download_id] = manager; |
| else |
| NOTREACHED(); |
| |
| // StartDownload will clean up |info|. |
| manager->StartDownload(info); |
| } |
| |
| // Update the Download Manager with the finish state, and remove the request |
| // tracking entries. |
| void DownloadFileManager::OnDownloadFinished(int id, |
| int64 bytes_so_far) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| DownloadManager* manager = LookupManager(id); |
| if (manager) |
| manager->DownloadFinished(id, bytes_so_far); |
| RemoveDownload(id, manager); |
| RemoveDownloadFromUIProgress(id); |
| } |
| |
| void DownloadFileManager::DownloadUrl( |
| const GURL& url, |
| const GURL& referrer, |
| const std::string& referrer_charset, |
| const DownloadSaveInfo& save_info, |
| int render_process_host_id, |
| int render_view_id, |
| URLRequestContextGetter* request_context_getter) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| ChromeThread::PostTask( |
| ChromeThread::IO, FROM_HERE, |
| NewRunnableMethod(this, |
| &DownloadFileManager::OnDownloadUrl, |
| url, |
| referrer, |
| referrer_charset, |
| save_info, |
| render_process_host_id, |
| render_view_id, |
| request_context_getter)); |
| } |
| |
| // Relate a download ID to its owning DownloadManager. |
| DownloadManager* DownloadFileManager::LookupManager(int download_id) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| DownloadManagerMap::iterator it = managers_.find(download_id); |
| if (it != managers_.end()) |
| return it->second; |
| return NULL; |
| } |
| |
| // Utility function for look up table maintenance, called on the UI thread. |
| // A manager may have multiple downloads in progress, so we just look up the |
| // one download (id) and remove it from the set, and remove the set if it |
| // becomes empty. |
| void DownloadFileManager::RemoveDownload(int id, DownloadManager* manager) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| if (manager) { |
| RequestMap::iterator it = requests_.find(manager); |
| if (it != requests_.end()) { |
| DownloadRequests& downloads = it->second; |
| DownloadRequests::iterator rit = downloads.find(id); |
| if (rit != downloads.end()) |
| downloads.erase(rit); |
| if (downloads.empty()) |
| requests_.erase(it); |
| } |
| } |
| |
| // A download can only have one manager, so remove it if it exists. |
| DownloadManagerMap::iterator dit = managers_.find(id); |
| if (dit != managers_.end()) |
| managers_.erase(dit); |
| } |
| |
| // Utility function for converting request IDs to a TabContents. Must be called |
| // only on the UI thread since Profile operations may create UI objects, such as |
| // the first call to profile->GetDownloadManager(). |
| // static |
| DownloadManager* DownloadFileManager::DownloadManagerFromRenderIds( |
| int render_process_id, int render_view_id) { |
| TabContents* contents = tab_util::GetTabContentsByID(render_process_id, |
| render_view_id); |
| if (contents) { |
| Profile* profile = contents->profile(); |
| if (profile) |
| return profile->GetDownloadManager(); |
| } |
| |
| return NULL; |
| } |
| |
| // Called by DownloadManagers in their destructor, and only on the UI thread. |
| void DownloadFileManager::RemoveDownloadManager(DownloadManager* manager) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| DCHECK(manager); |
| RequestMap::iterator it = requests_.find(manager); |
| if (it == requests_.end()) |
| return; |
| |
| const DownloadRequests& requests = it->second; |
| DownloadRequests::const_iterator i = requests.begin(); |
| for (; i != requests.end(); ++i) { |
| DownloadManagerMap::iterator dit = managers_.find(*i); |
| if (dit != managers_.end()) { |
| DCHECK(dit->second == manager); |
| managers_.erase(dit); |
| } |
| } |
| |
| requests_.erase(it); |
| } |
| |
| |
| // Notifications from the UI thread and run on the IO thread |
| |
| // Initiate a request for URL to be downloaded. |
| void DownloadFileManager::OnDownloadUrl( |
| const GURL& url, |
| const GURL& referrer, |
| const std::string& referrer_charset, |
| const DownloadSaveInfo& save_info, |
| int render_process_host_id, |
| int render_view_id, |
| URLRequestContextGetter* request_context_getter) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
| |
| URLRequestContext* context = request_context_getter->GetURLRequestContext(); |
| context->set_referrer_charset(referrer_charset); |
| |
| resource_dispatcher_host_->BeginDownload(url, |
| referrer, |
| save_info, |
| render_process_host_id, |
| render_view_id, |
| context); |
| } |
| |
| // Actions from the UI thread and run on the download thread |
| |
| // Open a download, or show it in a file explorer window. We run on this |
| // thread to avoid blocking the UI with (potentially) slow Shell operations. |
| // TODO(paulg): File 'stat' operations. |
| #if !defined(OS_MACOSX) |
| void DownloadFileManager::OnShowDownloadInShell(const FilePath& full_path) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| platform_util::ShowItemInFolder(full_path); |
| } |
| #endif |
| |
| // Launches the selected download using ShellExecute 'open' verb. For windows, |
| // if there is a valid parent window, the 'safer' version will be used which can |
| // display a modal dialog asking for user consent on dangerous files. |
| #if !defined(OS_MACOSX) |
| void DownloadFileManager::OnOpenDownloadInShell(const FilePath& full_path, |
| const GURL& url, |
| gfx::NativeView parent_window) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| #if defined(OS_WIN) |
| if (NULL != parent_window) { |
| win_util::SaferOpenItemViaShell(parent_window, L"", full_path, |
| UTF8ToWide(url.spec())); |
| return; |
| } |
| #endif |
| platform_util::OpenItem(full_path); |
| } |
| #endif // OS_MACOSX |
| |
| // 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. |
| void DownloadFileManager::OnFinalDownloadName(int id, |
| const FilePath& full_path, |
| DownloadManager* manager) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); |
| DownloadFileMap::iterator it = downloads_.find(id); |
| if (it == downloads_.end()) |
| return; |
| |
| file_util::CreateDirectory(full_path.DirName()); |
| |
| DownloadFile* download = it->second; |
| if (download->Rename(full_path)) { |
| #if defined(OS_MACOSX) |
| // Done here because we only want to do this once; see |
| // http://crbug.com/13120 for details. |
| download->AnnotateWithSourceInformation(); |
| #endif |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod( |
| 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. |
| DownloadManagerMap::iterator dmit = managers_.find(download->id()); |
| if (dmit != managers_.end()) { |
| DownloadManager* dlm = dmit->second; |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod(dlm, &DownloadManager::DownloadCancelled, id)); |
| } else { |
| ChromeThread::PostTask( |
| ChromeThread::IO, FROM_HERE, |
| NewRunnableFunction(&DownloadManager::OnCancelDownloadRequest, |
| resource_dispatcher_host_, |
| download->child_id(), |
| download->request_id())); |
| } |
| } |
| |
| // 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(it); |
| delete download; |
| } |
| |
| if (downloads_.empty()) { |
| ChromeThread::PostTask( |
| ChromeThread::UI, FROM_HERE, |
| NewRunnableMethod(this, &DownloadFileManager::StopUpdateTimer)); |
| } |
| } |
| |
| // static |
| void DownloadFileManager::DeleteFile(const FilePath& path) { |
| // Make sure we only delete files. |
| if (!file_util::DirectoryExists(path)) |
| file_util::Delete(path, false); |
| } |