Index: chrome/browser/download_file.cc |
=================================================================== |
--- chrome/browser/download_file.cc (revision 2162) |
+++ chrome/browser/download_file.cc (working copy) |
@@ -1,580 +0,0 @@ |
-// 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 <Windows.h> |
-#include <objbase.h> |
- |
-#include "chrome/browser/download_file.h" |
- |
-#include "base/file_util.h" |
-#include "base/path_service.h" |
-#include "base/scoped_ptr.h" |
-#include "base/string_util.h" |
-#include "base/task.h" |
-#include "chrome/browser/browser_process.h" |
-#include "chrome/browser/download_manager.h" |
-#include "chrome/browser/profile.h" |
-#include "chrome/browser/resource_dispatcher_host.h" |
-#include "chrome/browser/tab_contents.h" |
-#include "chrome/browser/tab_util.h" |
-#include "chrome/common/chrome_paths.h" |
-#include "chrome/common/stl_util-inl.h" |
-#include "chrome/common/win_util.h" |
-#include "chrome/common/win_safe_util.h" |
-#include "googleurl/src/gurl.h" |
-#include "net/base/net_util.h" |
-#include "net/url_request/url_request_context.h" |
- |
-// Throttle updates to the UI thread so that a fast moving download doesn't |
-// cause it to become unresponsive (ins 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_EVIL_CONSTRUCTORS(DownloadFileUpdateTask); |
-}; |
- |
-// DownloadFile implementation ------------------------------------------------- |
- |
-DownloadFile::DownloadFile(const DownloadCreateInfo* info) |
- : file_(NULL), |
- id_(info->download_id), |
- render_process_id_(info->render_process_id), |
- render_view_id_(info->render_view_id), |
- request_id_(info->request_id), |
- bytes_so_far_(0), |
- path_renamed_(false), |
- in_progress_(true) { |
-} |
- |
-DownloadFile::~DownloadFile() { |
- Close(); |
-} |
- |
-bool DownloadFile::Initialize() { |
- if (file_util::CreateTemporaryFileName(&full_path_)) |
- return Open(L"wb"); |
- return false; |
-} |
- |
-// FIXME bug 595247: handle errors on file writes. |
-bool DownloadFile::AppendDataToFile(const char* data, int data_len) { |
- if (file_) { |
- fwrite(data, 1, data_len, file_); |
- bytes_so_far_ += data_len; |
- return true; |
- } |
- return false; |
-} |
- |
-void DownloadFile::Cancel() { |
- Close(); |
- DeleteFile(full_path_.c_str()); |
-} |
- |
-// The UI has provided us with our finalized name. |
-bool DownloadFile::Rename(const std::wstring& new_path) { |
- Close(); |
- |
- // 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_.c_str(), |
- new_path.c_str())) { |
- return false; |
- } |
- |
- DeleteFile(full_path_.c_str()); |
- |
- 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(L"a+b")) |
- return false; |
- return true; |
-} |
- |
-void DownloadFile::Close() { |
- if (file_) { |
- fclose(file_); |
- file_ = NULL; |
- } |
-} |
- |
-bool DownloadFile::Open(const wchar_t* open_mode) { |
- DCHECK(!full_path_.empty()); |
- if (_wfopen_s(&file_, full_path_.c_str(), open_mode)) { |
- file_ = NULL; |
- return false; |
- } |
- // 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_); |
- return true; |
-} |
- |
-// DownloadFileManager implementation ------------------------------------------ |
- |
-DownloadFileManager::DownloadFileManager(MessageLoop* ui_loop, |
- ResourceDispatcherHost* rdh) |
- : next_id_(0), |
- ui_loop_(ui_loop), |
- resource_dispatcher_host_(rdh) { |
-} |
- |
-DownloadFileManager::~DownloadFileManager() { |
- // Check for clean shutdown. |
- DCHECK(downloads_.empty()); |
- ui_progress_.clear(); |
-} |
- |
-void DownloadFileManager::Initialize() { |
- io_loop_ = g_browser_process->io_thread()->message_loop(); |
- file_loop_ = g_browser_process->file_thread()->message_loop(); |
-} |
- |
-// Called during the browser shutdown process to clean up any state (open files, |
-// timers) that live on the download_thread_. |
-void DownloadFileManager::Shutdown() { |
- DCHECK(MessageLoop::current() == ui_loop_); |
- StopUpdateTimer(); |
- file_loop_->PostTask(FROM_HERE, |
- NewRunnableMethod(this, |
- &DownloadFileManager::OnShutdown)); |
-} |
- |
-// Cease download thread operations. |
-void DownloadFileManager::OnShutdown() { |
- DCHECK(MessageLoop::current() == file_loop_); |
- // 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(MessageLoop::current() == ui_loop_); |
- 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(MessageLoop::current() == ui_loop_); |
- if (!update_timer_.IsRunning()) { |
- update_timer_.Start(TimeDelta::FromMilliseconds(kUpdatePeriodMs), this, |
- &DownloadFileManager::UpdateInProgressDownloads); |
- } |
-} |
- |
-void DownloadFileManager::StopUpdateTimer() { |
- DCHECK(MessageLoop::current() == ui_loop_); |
- update_timer_.Stop(); |
-} |
- |
-// Called on the IO thread once the ResourceDispatcherHost has decided that a |
-// request is a download. |
-int DownloadFileManager::GetNextId() { |
- DCHECK(MessageLoop::current() == io_loop_); |
- 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(MessageLoop::current() == file_loop_); |
- 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. |
- ui_loop_->PostTask(FROM_HERE, |
- NewRunnableFunction(&DownloadManager::CancelDownloadRequest, |
- info->render_process_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; |
- } |
- |
- ui_loop_->PostTask(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(MessageLoop::current() == file_loop_); |
- 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) { |
- char* data = contents[i].first; |
- const int data_len = contents[i].second; |
- if (download) |
- download->AppendDataToFile(data, data_len); |
- delete [] data; |
- } |
- |
- if (download) { |
- AutoLock lock(progress_lock_); |
- ui_progress_[download->id()] = download->bytes_so_far(); |
- } |
-} |
- |
-void DownloadFileManager::DownloadFinished(int id, DownloadBuffer* buffer) { |
- DCHECK(MessageLoop::current() == file_loop_); |
- delete buffer; |
- DownloadFileMap::iterator it = downloads_.find(id); |
- if (it != downloads_.end()) { |
- DownloadFile* download = it->second; |
- download->set_in_progress(false); |
- |
- ui_loop_->PostTask(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()) |
- ui_loop_->PostTask(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(MessageLoop::current() == file_loop_); |
- DownloadFileMap::iterator it = downloads_.find(id); |
- if (it != downloads_.end()) { |
- DownloadFile* download = it->second; |
- download->set_in_progress(false); |
- |
- download->Cancel(); |
- |
- ui_loop_->PostTask(FROM_HERE, |
- NewRunnableMethod(this, |
- &DownloadFileManager::RemoveDownloadFromUIProgress, |
- download->id())); |
- |
- if (download->path_renamed()) { |
- downloads_.erase(it); |
- delete download; |
- } |
- } |
- |
- if (downloads_.empty()) |
- ui_loop_->PostTask(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(MessageLoop::current() == ui_loop_); |
- 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 WebContents' 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(MessageLoop::current() == ui_loop_); |
- DownloadManager* manager = |
- DownloadManagerFromRenderIds(info->render_process_id, |
- info->render_view_id); |
- if (!manager) { |
- DownloadManager::CancelDownloadRequest(info->render_process_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(MessageLoop::current() == ui_loop_); |
- 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, |
- int render_process_host_id, |
- int render_view_id, |
- URLRequestContext* request_context) { |
- DCHECK(MessageLoop::current() == ui_loop_); |
- base::Thread* thread = g_browser_process->io_thread(); |
- if (thread) { |
- thread->message_loop()->PostTask(FROM_HERE, |
- NewRunnableMethod(this, |
- &DownloadFileManager::OnDownloadUrl, |
- url, |
- referrer, |
- render_process_host_id, |
- render_view_id, |
- request_context)); |
- } |
-} |
- |
-// Relate a download ID to its owning DownloadManager. |
-DownloadManager* DownloadFileManager::LookupManager(int download_id) { |
- DCHECK(MessageLoop::current() == ui_loop_); |
- 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(MessageLoop::current() == ui_loop_); |
- 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 && contents->type() == TAB_CONTENTS_WEB) { |
- 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(MessageLoop::current() == ui_loop_); |
- 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, |
- int render_process_host_id, |
- int render_view_id, |
- URLRequestContext* request_context) { |
- DCHECK(MessageLoop::current() == io_loop_); |
- resource_dispatcher_host_->BeginDownload(url, |
- referrer, |
- render_process_host_id, |
- render_view_id, |
- request_context); |
-} |
- |
-// Actions from the UI thread and run on the download thread |
- |
-// Open a download, or show it in a Windows Explorer window. We run on this |
-// thread to avoid blocking the UI with (potentially) slow Shell operations. |
-// TODO(paulg): File 'stat' operations. |
-void DownloadFileManager::OnShowDownloadInShell(const std::wstring full_path) { |
- DCHECK(MessageLoop::current() == file_loop_); |
- win_util::ShowItemInFolder(full_path); |
-} |
- |
-// Launches the selected download using ShellExecute 'open' verb. 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. |
-void DownloadFileManager::OnOpenDownloadInShell(const std::wstring full_path, |
- const std::wstring& url, |
- HWND parent_window) { |
- DCHECK(MessageLoop::current() == file_loop_); |
- if (NULL != parent_window) { |
- win_util::SaferOpenItemViaShell(parent_window, L"", full_path, url, true); |
- } else { |
- win_util::OpenItemViaShell(full_path, true); |
- } |
-} |
- |
-// 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 std::wstring& full_path) { |
- DCHECK(MessageLoop::current() == file_loop_); |
- DownloadFileMap::iterator it = downloads_.find(id); |
- if (it == downloads_.end()) |
- return; |
- |
- std::wstring download_dir = file_util::GetDirectoryFromPath(full_path); |
- if (!file_util::PathExists(download_dir)) |
- file_util::CreateDirectory(download_dir); |
- |
- DownloadFile* download = it->second; |
- if (!download->Rename(full_path)) { |
- // 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; |
- ui_loop_->PostTask(FROM_HERE, |
- NewRunnableMethod(dlm, |
- &DownloadManager::DownloadCancelled, |
- id)); |
- } else { |
- ui_loop_->PostTask(FROM_HERE, |
- NewRunnableFunction(&DownloadManager::CancelDownloadRequest, |
- download->render_process_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()) |
- ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( |
- this, &DownloadFileManager::StopUpdateTimer)); |
-} |
- |
-void DownloadFileManager::CreateDirectory(const std::wstring& directory) { |
- if (!file_util::PathExists(directory)) |
- file_util::CreateDirectory(directory); |
-} |