diff options
author | paulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-09-13 02:48:59 +0000 |
---|---|---|
committer | paulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-09-13 02:48:59 +0000 |
commit | cdaa86594dcdd5fc4ccac5dbf63a5e4dbfcdb8ea (patch) | |
tree | c9e3f5a89a3d9733ea75b97153ef2be4e8425aa3 /chrome/browser/download | |
parent | 431838df78d6e3cd06794a45cf0243be32149c7c (diff) | |
download | chromium_src-cdaa86594dcdd5fc4ccac5dbf63a5e4dbfcdb8ea.zip chromium_src-cdaa86594dcdd5fc4ccac5dbf63a5e4dbfcdb8ea.tar.gz chromium_src-cdaa86594dcdd5fc4ccac5dbf63a5e4dbfcdb8ea.tar.bz2 |
Move the download code to new directories:
browser/download/
browser/views/
Review URL: http://codereview.chromium.org/2826
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2166 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/download')
-rw-r--r-- | chrome/browser/download/download_exe.cc | 137 | ||||
-rw-r--r-- | chrome/browser/download/download_file.cc | 580 | ||||
-rw-r--r-- | chrome/browser/download/download_file.h | 273 | ||||
-rw-r--r-- | chrome/browser/download/download_item_model.cc | 89 | ||||
-rw-r--r-- | chrome/browser/download/download_item_model.h | 34 | ||||
-rw-r--r-- | chrome/browser/download/download_manager.cc | 1104 | ||||
-rw-r--r-- | chrome/browser/download/download_manager.h | 483 | ||||
-rw-r--r-- | chrome/browser/download/download_manager_unittest.cc | 320 | ||||
-rw-r--r-- | chrome/browser/download/download_uitest.cc | 241 | ||||
-rw-r--r-- | chrome/browser/download/download_util.cc | 407 | ||||
-rw-r--r-- | chrome/browser/download/download_util.h | 192 |
11 files changed, 3860 insertions, 0 deletions
diff --git a/chrome/browser/download/download_exe.cc b/chrome/browser/download/download_exe.cc new file mode 100644 index 0000000..c9fdc4d --- /dev/null +++ b/chrome/browser/download/download_exe.cc @@ -0,0 +1,137 @@ +// 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 <set> +#include <string> + +#include "chrome/browser/download/download_util.h" + +namespace download_util { + +// For file extensions taken from mozilla: + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Doug Turner <dougt@netscape.com> + * Dean Tessman <dean_tessman@hotmail.com> + * Brodie Thiesfield <brofield@jellycan.com> + * Jungshik Shin <jshin@i18nl10n.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +static const wchar_t* const g_executables[] = { + L"ad", + L"ade", + L"adp", + L"app", + L"application", + L"asp", + L"bas", + L"bat", + L"chm", + L"cmd", + L"com", + L"cpl", + L"crt", + L"exe", + L"fxp", + L"hlp", + L"hta", + L"inf", + L"ins", + L"isp", + L"js", + L"jse", + L"lnk", + L"mad", + L"maf", + L"mag", + L"mam", + L"maq", + L"mar", + L"mas", + L"mat", + L"mau", + L"mav", + L"maw", + L"mda", + L"mdb", + L"mde", + L"mdt", + L"mdw", + L"mdz", + L"msc", + L"msh", + L"mshxml", + L"msi", + L"msp", + L"mst", + L"ops", + L"pcd", + L"pif", + L"plg", + L"prf", + L"prg", + L"pst", + L"reg", + L"scf", + L"scr", + L"sct", + L"shb", + L"shs", + L"url", + L"vb", + L"vbe", + L"vbs", + L"vsd", + L"vsmacros", + L"vss", + L"vst", + L"vsw", + L"ws", + L"wsc", + L"wsf", + L"wsh" +}; + +void InitializeExeTypes(std::set<std::wstring>* exe_extensions) { + DCHECK(exe_extensions); + for (size_t i = 0; i < arraysize(g_executables); ++i) + exe_extensions->insert(g_executables[i]); +} + +} // namespace download_util + diff --git a/chrome/browser/download/download_file.cc b/chrome/browser/download/download_file.cc new file mode 100644 index 0000000..99f5caa --- /dev/null +++ b/chrome/browser/download/download_file.cc @@ -0,0 +1,580 @@ +// 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/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/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); +} diff --git a/chrome/browser/download/download_file.h b/chrome/browser/download/download_file.h new file mode 100644 index 0000000..5a6158d --- /dev/null +++ b/chrome/browser/download/download_file.h @@ -0,0 +1,273 @@ +// 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. +// +// Objects that handle file operations for downloads, on the download thread. +// +// The DownloadFileManager owns a set of DownloadFile objects, each of which +// represent one in progress download and performs the disk IO for that +// download. The DownloadFileManager itself is a singleton object owned by the +// ResourceDispatcherHost. +// +// The DownloadFileManager uses the file_thread for performing file write +// operations, in order to avoid disk activity on either the IO (network) thread +// and the UI thread. It coordinates the notifications from the network and UI. +// +// A typical download operation involves multiple threads: +// +// Updating an in progress download +// io_thread +// |----> data ---->| +// file_thread (writes to disk) +// |----> stats ---->| +// ui_thread (feedback for user and +// updates to history) +// +// Cancel operations perform the inverse order when triggered by a user action: +// ui_thread (user click) +// |----> cancel command ---->| +// file_thread (close file) +// |----> cancel command ---->| +// io_thread (stops net IO +// for download) +// +// The DownloadFileManager tracks download requests, mapping from a download +// ID (unique integer created in the IO thread) to the DownloadManager for the +// tab (profile) where the download was initiated. In the event of a tab closure +// during a download, the DownloadFileManager will continue to route data to the +// appropriate DownloadManager. In progress downloads are cancelled for a +// DownloadManager that exits (such as when closing a profile). + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_FILE_H__ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_FILE_H__ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/lock.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "base/timer.h" +#include "chrome/browser/history/download_types.h" + +class DownloadManager; +class GURL; +class MessageLoop; +class ResourceDispatcherHost; +class URLRequestContext; + +// DownloadBuffer -------------------------------------------------------------- + +// This container is created and populated on the io_thread, and passed to the +// file_thread for writing. In order to avoid flooding the file_thread with too +// many small write messages, each write is appended to the DownloadBuffer while +// waiting for the task to run on the file_thread. Access to the write buffers +// is synchronized via the lock. Each entry in 'contents' represents one data +// buffer and its size in bytes. + +struct DownloadBuffer { + Lock lock; + typedef std::pair<char *, int> Contents; + std::vector<Contents> contents; +}; + +// DownloadFile ---------------------------------------------------------------- + +// These objects live exclusively on the download thread and handle the writing +// operations for one download. These objects live only for the duration that +// the download is 'in progress': once the download has been completed or +// cancelled, the DownloadFile is destroyed. +class DownloadFile { + public: + DownloadFile(const DownloadCreateInfo* info); + ~DownloadFile(); + + bool Initialize(); + + // Write a new chunk of data to the file. Returns true on success. + bool AppendDataToFile(const char* data, int data_len); + + // Abort the download and automatically close the file. + void Cancel(); + + // Rename the download file. Returns 'true' if the rename was successful. + bool Rename(const std::wstring& full_path); + + // Accessors. + int64 bytes_so_far() const { return bytes_so_far_; } + int id() const { return id_; } + std::wstring full_path() const { return full_path_; } + int render_process_id() const { return render_process_id_; } + int render_view_id() const { return render_view_id_; } + int request_id() const { return request_id_; } + bool path_renamed() const { return path_renamed_; } + bool in_progress() const { return file_ != NULL; } + void set_in_progress(bool in_progress) { in_progress_ = in_progress; } + + private: + // Open or Close the OS file handle. The file is opened in the constructor + // based on creation information passed to it, and automatically closed in + // the destructor. + void Close(); + bool Open(const wchar_t* open_mode); + + // OS file handle for writing + FILE* file_; + + // The unique identifier for this download, assigned at creation by + // the DownloadFileManager for its internal record keeping. + int id_; + + // IDs for looking up the tab we are associated with. + int render_process_id_; + int render_view_id_; + + // Handle for informing the ResourceDispatcherHost of a UI based cancel. + int request_id_; + + // Amount of data received up to this point. We may not know in advance how + // much data to expect since some servers don't provide that information. + int64 bytes_so_far_; + + // Full path to the downloaded file. + std::wstring full_path_; + + // Whether the download is still using its initial temporary path. + bool path_renamed_; + + // Whether the download is still receiving data. + bool in_progress_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadFile); +}; + + +// DownloadFileManager --------------------------------------------------------- + +// Manages all in progress downloads. +class DownloadFileManager + : public base::RefCountedThreadSafe<DownloadFileManager> { + public: + DownloadFileManager(MessageLoop* ui_loop, ResourceDispatcherHost* rdh); + ~DownloadFileManager(); + + // Lifetime management functions, called on the UI thread. + void Initialize(); + void Shutdown(); + + // Called on the IO thread + int GetNextId(); + + // Handlers for notifications sent from the IO thread and run on the + // download thread. + void StartDownload(DownloadCreateInfo* info); + void UpdateDownload(int id, DownloadBuffer* buffer); + void CancelDownload(int id); + void DownloadFinished(int id, DownloadBuffer* buffer); + + // Handlers for notifications sent from the download thread and run on + // the UI thread. + void OnStartDownload(DownloadCreateInfo* info); + void OnDownloadFinished(int id, int64 bytes_so_far); + + // Download the URL. Called on the UI thread and forwarded to the + // ResourceDispatcherHost on the IO thread. + void DownloadUrl(const GURL& url, + const GURL& referrer, + int render_process_host_id, + int render_view_id, + URLRequestContext* request_context); + + // Run on the IO thread to initiate the download of a URL. + void OnDownloadUrl(const GURL& url, + const GURL& referrer, + int render_process_host_id, + int render_view_id, + URLRequestContext* request_context); + + // Called on the UI thread to remove a download item or manager. + void RemoveDownloadManager(DownloadManager* manager); + void RemoveDownload(int id, DownloadManager* manager); + + // Handler for shell operations sent from the UI to the download thread. + void OnShowDownloadInShell(const std::wstring full_path); + // Handler to open or execute a downloaded file. + void OnOpenDownloadInShell(const std::wstring full_path, + const std::wstring& url, HWND parent_window); + + // The download manager has provided a final name for a download. Sent from + // the UI thread and run on the download thread. + void OnFinalDownloadName(int id, const std::wstring& full_path); + + // Timer notifications. + void UpdateInProgressDownloads(); + + MessageLoop* file_loop() const { return file_loop_; } + + // Called by the download manager at initialization to ensure the default + // download directory exists. + void CreateDirectory(const std::wstring& directory); + + private: + // Timer helpers for updating the UI about the current progress of a download. + void StartUpdateTimer(); + void StopUpdateTimer(); + + // Clean up helper that runs on the download thread. + void OnShutdown(); + + // Called only on UI thread to get the DownloadManager for a tab's profile. + static DownloadManager* DownloadManagerFromRenderIds(int render_process_id, + int review_view_id); + DownloadManager* LookupManager(int download_id); + + // Called only on the download thread. + DownloadFile* LookupDownload(int id); + + // Called on the UI thread to remove a download from the UI progress table. + void RemoveDownloadFromUIProgress(int id); + + // Unique ID for each DownloadFile. + int next_id_; + + // A map of all in progress downloads. + typedef base::hash_map<int, DownloadFile*> DownloadFileMap; + DownloadFileMap downloads_; + + // Throttle updates to the UI thread. + base::RepeatingTimer<DownloadFileManager> update_timer_; + + // The MessageLoop that the DownloadManagers live on. + MessageLoop* ui_loop_; + + // The MessageLoop that the this objects primarily operates on. + MessageLoop* file_loop_; + + // Used only for DCHECKs! + MessageLoop* io_loop_; + + ResourceDispatcherHost* resource_dispatcher_host_; + + // Tracking which DownloadManager to send data to, called only on UI thread. + // DownloadManagerMap maps download IDs to their DownloadManager. + typedef base::hash_map<int, DownloadManager*> DownloadManagerMap; + DownloadManagerMap managers_; + + // RequestMap maps a DownloadManager to all in-progress download IDs. + // Called only on the UI thread. + typedef base::hash_set<int> DownloadRequests; + typedef base::hash_map<DownloadManager*, DownloadRequests> RequestMap; + RequestMap requests_; + + // Used for progress updates on the UI thread, mapping download->id() to bytes + // received so far. Written to by the file thread and read by the UI thread. + typedef base::hash_map<int, int64> ProgressMap; + ProgressMap ui_progress_; + Lock progress_lock_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadFileManager); +}; + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_FILE_H__ diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc new file mode 100644 index 0000000..db93b35 --- /dev/null +++ b/chrome/browser/download/download_item_model.cc @@ -0,0 +1,89 @@ +// 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_item_model.h" + +#include "base/string_util.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/time_format.h" + +#include "generated_resources.h" + +DownloadItemModel::DownloadItemModel(DownloadItem* download) + : download_(download) { +} + +void DownloadItemModel::CancelTask() { + download_->Cancel(true /* update history service */); +} + +std::wstring DownloadItemModel::GetStatusText() { + int64 size = download_->received_bytes(); + int64 total = download_->total_bytes(); + + DataUnits amount_units = GetByteDisplayUnits(total); + const std::wstring simple_size = FormatBytes(size, amount_units, false); + + // In RTL locales, we render the text "size/total" in an RTL context. This + // is problematic since a string such as "123/456 MB" is displayed + // as "MB 123/456" because it ends with an LTR run. In order to solve this, + // we mark the total string as an LTR string if the UI layout is + // right-to-left so that the string "456 MB" is treated as an LTR run. + std::wstring simple_total = FormatBytes(total, amount_units, true); + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + l10n_util::WrapStringWithLTRFormatting(&simple_total); + + TimeDelta remaining; + std::wstring simple_time; + if (download_->state() == DownloadItem::IN_PROGRESS && + download_->is_paused()) { + simple_time = l10n_util::GetString(IDS_DOWNLOAD_PROGRESS_PAUSED); + } else if (download_->TimeRemaining(&remaining)) { + simple_time = download_->open_when_complete() ? + TimeFormat::TimeRemainingShort(remaining) : + TimeFormat::TimeRemaining(remaining); + } + + std::wstring status_text; + switch (download_->state()) { + case DownloadItem::IN_PROGRESS: + if (download_->open_when_complete()) { + if (simple_time.empty()) { + status_text = + l10n_util::GetString(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE); + } else { + status_text = l10n_util::GetStringF(IDS_DOWNLOAD_STATUS_OPEN_IN, + simple_time); + } + } else { + if (simple_time.empty()) { + // Instead of displaying "0 B" we keep the "Starting..." string. + status_text = (size == 0) ? + l10n_util::GetString(IDS_DOWNLOAD_STATUS_STARTING) : + FormatBytes(size, GetByteDisplayUnits(size), true); + } else { + status_text = l10n_util::GetStringF(IDS_DOWNLOAD_STATUS_IN_PROGRESS, + simple_size, + simple_total, + simple_time); + } + } + break; + case DownloadItem::COMPLETE: + status_text.clear(); + break; + case DownloadItem::CANCELLED: + status_text = l10n_util::GetStringF(IDS_DOWNLOAD_STATUS_CANCELLED, + simple_size); + break; + case DownloadItem::REMOVING: + break; + default: + NOTREACHED(); + } + + return status_text; +} + diff --git a/chrome/browser/download/download_item_model.h b/chrome/browser/download/download_item_model.h new file mode 100644 index 0000000..edeac1c --- /dev/null +++ b/chrome/browser/download/download_item_model.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_MODEL_H__ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_MODEL_H__ + +#include "chrome/browser/views/download_item_view.h" + +class DownloadItem; + +// This class is a model class for DownloadItemView. It provides functionality +// for canceling the downloading, and also the text for displaying downloading +// status. +class DownloadItemModel : public DownloadItemView::BaseDownloadItemModel { + public: + DownloadItemModel(DownloadItem* download); + virtual ~DownloadItemModel() { } + + // Cancel the downloading. + virtual void CancelTask(); + + // Get downloading status text. + virtual std::wstring GetStatusText(); + + private: + // We query this item for status information. + DownloadItem* download_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadItemModel); +}; + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_MODEL_H__ + diff --git a/chrome/browser/download/download_manager.cc b/chrome/browser/download/download_manager.cc new file mode 100644 index 0000000..d83ddc2 --- /dev/null +++ b/chrome/browser/download/download_manager.cc @@ -0,0 +1,1104 @@ +// 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 <time.h> + +#include "chrome/browser/download/download_manager.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/registry.h" +#include "base/string_util.h" +#include "base/task.h" +#include "base/thread.h" +#include "base/timer.h" +#include "base/win_util.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/download/download_file.h" +#include "chrome/browser/download/download_util.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/render_process_host.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/resource_dispatcher_host.h" +#include "chrome/browser/tab_util.h" +#include "chrome/browser/web_contents.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/stl_util-inl.h" +#include "chrome/common/win_util.h" +#include "googleurl/src/gurl.h" +#include "net/base/mime_util.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request_context.h" + +#include "generated_resources.h" + +// Periodically update our observers. +class DownloadItemUpdateTask : public Task { + public: + explicit DownloadItemUpdateTask(DownloadItem* item) : item_(item) {} + void Run() { if (item_) item_->UpdateObservers(); } + + private: + DownloadItem* item_; +}; + +// Update frequency (milliseconds). +static const int kUpdateTimeMs = 1000; + +// Our download table ID starts at 1, so we use 0 to represent a download that +// has started, but has not yet had its data persisted in the table. We use fake +// database handles in incognito mode starting at -1 and progressly getting more +// negative. +static const int kUninitializedHandle = 0; + +// Attempts to modify |path| to be a non-existing path. +// Returns true if |path| points to a non-existing path upon return. +static bool UniquifyPath(std::wstring* path) { + DCHECK(path); + const int kMaxAttempts = 100; + + if (!file_util::PathExists(*path)) + return true; + + std::wstring new_path; + for (int count = 1; count <= kMaxAttempts; ++count) { + new_path.assign(*path); + file_util::InsertBeforeExtension(&new_path, StringPrintf(L" (%d)", count)); + + if (!file_util::PathExists(new_path)) { + path->swap(new_path); + return true; + } + } + + return false; +} + +static bool DownloadPathIsDangerous(const std::wstring& download_path) { + std::wstring desktop_dir; + if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_dir)) { + NOTREACHED(); + return false; + } + return (download_path == desktop_dir); +} + +// DownloadItem implementation ------------------------------------------------- + +// Constructor for reading from the history service. +DownloadItem::DownloadItem(const DownloadCreateInfo& info) + : id_(-1), + full_path_(info.path), + url_(info.url), + total_bytes_(info.total_bytes), + received_bytes_(info.received_bytes), + start_tick_(0), + state_(static_cast<DownloadState>(info.state)), + start_time_(info.start_time), + db_handle_(info.db_handle), + manager_(NULL), + is_paused_(false), + open_when_complete_(false), + render_process_id_(-1), + request_id_(-1) { + if (state_ == IN_PROGRESS) + state_ = CANCELLED; + Init(false /* don't start progress timer */); +} + +// Constructor for DownloadItem created via user action in the main thread. +DownloadItem::DownloadItem(int32 download_id, + const std::wstring& path, + const std::wstring& url, + const Time start_time, + int64 download_size, + int render_process_id, + int request_id) + : id_(download_id), + full_path_(path), + url_(url), + total_bytes_(download_size), + received_bytes_(0), + start_tick_(GetTickCount()), + state_(IN_PROGRESS), + start_time_(start_time), + db_handle_(kUninitializedHandle), + manager_(NULL), + is_paused_(false), + open_when_complete_(false), + render_process_id_(render_process_id), + request_id_(request_id) { + Init(true /* start progress timer */); +} + +void DownloadItem::Init(bool start_timer) { + file_name_ = file_util::GetFilenameFromPath(full_path_); + if (start_timer) + StartProgressTimer(); +} + +DownloadItem::~DownloadItem() { + state_ = REMOVING; + UpdateObservers(); +} + +void DownloadItem::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void DownloadItem::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void DownloadItem::UpdateObservers() { + FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this)); +} + +// If we've received more data than we were expecting (bad server info?), revert +// to 'unknown size mode'. +void DownloadItem::UpdateSize(int64 bytes_so_far) { + received_bytes_ = bytes_so_far; + if (received_bytes_ > total_bytes_) + total_bytes_ = 0; +} + +// Updates from the download thread may have been posted while this download +// was being cancelled in the UI thread, so we'll accept them unless we're +// complete. +void DownloadItem::Update(int64 bytes_so_far) { + if (state_ == COMPLETE) { + NOTREACHED(); + return; + } + UpdateSize(bytes_so_far); + UpdateObservers(); +} + +// Triggered by a user action +void DownloadItem::Cancel(bool update_history) { + if (state_ != IN_PROGRESS) { + // Small downloads might be complete before this method has a chance to run. + return; + } + state_ = CANCELLED; + UpdateObservers(); + StopProgressTimer(); + if (update_history) + manager_->DownloadCancelled(id_); +} + +void DownloadItem::Finished(int64 size) { + state_ = COMPLETE; + UpdateSize(size); + UpdateObservers(); + StopProgressTimer(); +} + +void DownloadItem::Remove() { + Cancel(true); + state_ = REMOVING; + manager_->RemoveDownload(db_handle_); +} + +void DownloadItem::StartProgressTimer() { + update_timer_.Start(TimeDelta::FromMilliseconds(kUpdateTimeMs), this, + &DownloadItem::UpdateObservers); +} + +void DownloadItem::StopProgressTimer() { + update_timer_.Stop(); +} + +bool DownloadItem::TimeRemaining(TimeDelta* remaining) const { + if (total_bytes_ <= 0) + return false; // We never received the content_length for this download. + + int64 speed = CurrentSpeed(); + if (speed == 0) + return false; + + *remaining = + TimeDelta::FromSeconds((total_bytes_ - received_bytes_) / speed); + return true; +} + +int64 DownloadItem::CurrentSpeed() const { + uintptr_t diff = GetTickCount() - start_tick_; + return diff == 0 ? 0 : received_bytes_ * 1000 / diff; +} + +int DownloadItem::PercentComplete() const { + int percent = -1; + if (total_bytes_ > 0) + percent = static_cast<int>(received_bytes_ * 100.0 / total_bytes_); + return percent; +} + +void DownloadItem::Rename(const std::wstring& full_path) { + DCHECK(!full_path.empty()); + full_path_ = full_path; + file_name_ = file_util::GetFilenameFromPath(full_path_); +} + +void DownloadItem::TogglePause() { + DCHECK(state_ == IN_PROGRESS); + manager_->PauseDownload(id_, !is_paused_); + is_paused_ = !is_paused_; + UpdateObservers(); +} + +// DownloadManager implementation ---------------------------------------------- + +// static +void DownloadManager::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(prefs::kPromptForDownload, false); + prefs->RegisterStringPref(prefs::kDownloadExtensionsToOpen, L""); + prefs->RegisterBooleanPref(prefs::kDownloadDirUpgraded, false); + + // The default download path is userprofile\download. + std::wstring default_download_path; + if (!PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_download_path)) { + NOTREACHED(); + } + file_util::AppendToPath(&default_download_path, + l10n_util::GetString(IDS_DOWNLOAD_DIRECTORY)); + prefs->RegisterStringPref(prefs::kDownloadDefaultDirectory, + default_download_path); + + // If the download path is dangerous we forcefully reset it. But if we do + // so we set a flag to make sure we only do it once, to avoid fighting + // the user if he really wants it on an unsafe place such as the desktop. + + if (!prefs->GetBoolean(prefs::kDownloadDirUpgraded)) { + std::wstring current_download_dir = + prefs->GetString(prefs::kDownloadDefaultDirectory); + if (DownloadPathIsDangerous(current_download_dir)) { + prefs->SetString(prefs::kDownloadDefaultDirectory, + default_download_path); + } + prefs->SetBoolean(prefs::kDownloadDirUpgraded, true); + } +} + +DownloadManager::DownloadManager() + : shutdown_needed_(false), + profile_(NULL), + file_manager_(NULL), + ui_loop_(MessageLoop::current()), + file_loop_(NULL) { +} + +DownloadManager::~DownloadManager() { + if (shutdown_needed_) + Shutdown(); +} + +void DownloadManager::Shutdown() { + DCHECK(shutdown_needed_) << "Shutdown called when not needed."; + + // Stop receiving download updates + file_manager_->RemoveDownloadManager(this); + + // Stop making history service requests + cancelable_consumer_.CancelAllRequests(); + + // 'in_progress_' may contain DownloadItems that have not finished the start + // complete (from the history service) and thus aren't in downloads_. + DownloadMap::iterator it = in_progress_.begin(); + for (; it != in_progress_.end(); ++it) { + DownloadItem* download = it->second; + if (download->state() == DownloadItem::IN_PROGRESS) { + download->Cancel(false); + UpdateHistoryForDownload(download); + } + if (download->db_handle() == kUninitializedHandle) { + // An invalid handle means that 'download' does not yet exist in + // 'downloads_', so we have to delete it here. + delete download; + } + } + + in_progress_.clear(); + STLDeleteValues(&downloads_); + + file_manager_ = NULL; + + // Save our file extensions to auto open. + SaveAutoOpens(); + + // Make sure the save as dialog doesn't notify us back if we're gone before + // it returns. + if (select_file_dialog_.get()) + select_file_dialog_->ListenerDestroyed(); + + shutdown_needed_ = false; +} + +// Issue a history query for downloads matching 'search_text'. If 'search_text' +// is empty, return all downloads that we know about. +void DownloadManager::GetDownloads(Observer* observer, + const std::wstring& search_text) { + DCHECK(observer); + + // Return a empty list if we've not yet received the set of downloads from the + // history system (we'll update all observers once we get that list in + // OnQueryDownloadEntriesComplete), or if there are no downloads at all. + std::vector<DownloadItem*> download_copy; + if (downloads_.empty()) { + observer->SetDownloads(download_copy); + return; + } + + // We already know all the downloads and there is no filter, so just return a + // copy to the observer. + if (search_text.empty()) { + download_copy.reserve(downloads_.size()); + for (DownloadMap::iterator it = downloads_.begin(); + it != downloads_.end(); ++it) { + download_copy.push_back(it->second); + } + + // We retain ownership of the DownloadItems. + observer->SetDownloads(download_copy); + return; + } + + // Issue a request to the history service for a list of downloads matching + // our search text. + HistoryService* hs = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) { + HistoryService::Handle h = + hs->SearchDownloads(search_text, + &cancelable_consumer_, + NewCallback(this, + &DownloadManager::OnSearchComplete)); + cancelable_consumer_.SetClientData(hs, h, observer); + } +} + +// Query the history service for information about all persisted downloads. +bool DownloadManager::Init(Profile* profile) { + DCHECK(profile); + DCHECK(!shutdown_needed_) << "DownloadManager already initialized."; + shutdown_needed_ = true; + + profile_ = profile; + request_context_ = profile_->GetRequestContext(); + + // 'incognito mode' will have access to past downloads, but we won't store + // information about new downloads while in that mode. + QueryHistoryForDownloads(); + + ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); + if (!rdh) { + NOTREACHED(); + return false; + } + + file_manager_ = rdh->download_file_manager(); + if (!file_manager_) { + NOTREACHED(); + return false; + } + + file_loop_ = g_browser_process->file_thread()->message_loop(); + if (!file_loop_) { + NOTREACHED(); + return false; + } + + // Get our user preference state. + PrefService* prefs = profile_->GetPrefs(); + DCHECK(prefs); + prompt_for_download_.Init(prefs::kPromptForDownload, prefs, NULL); + + download_path_.Init(prefs::kDownloadDefaultDirectory, prefs, NULL); + + // Ensure that the download directory specified in the preferences exists. + file_loop_->PostTask(FROM_HERE, NewRunnableMethod( + file_manager_, &DownloadFileManager::CreateDirectory, *download_path_)); + + // We store any file extension that should be opened automatically at + // download completion in this pref. + download_util::InitializeExeTypes(&exe_types_); + + std::wstring extensions_to_open = + prefs->GetString(prefs::kDownloadExtensionsToOpen); + std::vector<std::wstring> extensions; + SplitString(extensions_to_open, L':', &extensions); + for (size_t i = 0; i < extensions.size(); ++i) { + if (!extensions[i].empty() && !IsExecutable(extensions[i])) + auto_open_.insert(extensions[i]); + } + + return true; +} + +void DownloadManager::QueryHistoryForDownloads() { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) { + hs->QueryDownloads( + &cancelable_consumer_, + NewCallback(this, &DownloadManager::OnQueryDownloadEntriesComplete)); + } +} + +// We have received a message from DownloadFileManager about a new download. We +// create a download item and store it in our download map, and inform the +// history system of a new download. Since this method can be called while the +// history service thread is still reading the persistent state, we do not +// insert the new DownloadItem into 'downloads_' or inform our observers at this +// point. OnCreateDatabaseEntryComplete() handles that finalization of the the +// download creation as a callback from the history thread. +void DownloadManager::StartDownload(DownloadCreateInfo* info) { + DCHECK(MessageLoop::current() == ui_loop_); + DCHECK(info); + + // Determine the proper path for a download, by choosing either the default + // download directory, or prompting the user. + std::wstring generated_name; + GenerateFilename(info, &generated_name); + if (*prompt_for_download_ && !last_download_path_.empty()) + info->suggested_path = last_download_path_; + else + info->suggested_path = *download_path_; + file_util::AppendToPath(&info->suggested_path, generated_name); + + // We need to move over to the download thread because we don't want to stat + // the suggested path on the UI thread. + file_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, + &DownloadManager::CheckIfSuggestedPathExists, + info)); +} + +void DownloadManager::CheckIfSuggestedPathExists(DownloadCreateInfo* info) { + DCHECK(info); + + // Check writability of the suggested path. If we can't write to it, default + // to the user's "My Documents" directory. We'll prompt them in this case. + std::wstring path = file_util::GetDirectoryFromPath(info->suggested_path); + if (!file_util::PathIsWritable(path)) { + info->save_as = true; + const std::wstring filename = + file_util::GetFilenameFromPath(info->suggested_path); + PathService::Get(chrome::DIR_USER_DOCUMENTS, &info->suggested_path); + file_util::AppendToPath(&info->suggested_path, filename); + } + + info->suggested_path_exists = !UniquifyPath(&info->suggested_path); + + // Now we return to the UI thread. + ui_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, + &DownloadManager::OnPathExistenceAvailable, + info)); +} + +void DownloadManager::OnPathExistenceAvailable(DownloadCreateInfo* info) { + DCHECK(MessageLoop::current() == ui_loop_); + DCHECK(info); + + if (*prompt_for_download_ || info->save_as || info->suggested_path_exists) { + // We must ask the user for the place to put the download. + if (!select_file_dialog_.get()) + select_file_dialog_ = SelectFileDialog::Create(this); + + TabContents* contents = tab_util::GetTabContentsByID( + info->render_process_id, info->render_view_id); + HWND owning_hwnd = + contents ? GetAncestor(contents->GetContainerHWND(), GA_ROOT) : NULL; + select_file_dialog_->SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE, + std::wstring(), info->suggested_path, + owning_hwnd, info); + } else { + // No prompting for download, just continue with the suggested name. + ContinueStartDownload(info, info->suggested_path); + } +} + +void DownloadManager::ContinueStartDownload(DownloadCreateInfo* info, + const std::wstring& target_path) { + scoped_ptr<DownloadCreateInfo> infop(info); + info->path = target_path; + + DownloadItem* download = NULL; + DownloadMap::iterator it = in_progress_.find(info->download_id); + if (it == in_progress_.end()) { + download = new DownloadItem(info->download_id, + info->path, + info->url, + info->start_time, + info->total_bytes, + info->render_process_id, + info->request_id); + download->set_manager(this); + in_progress_[info->download_id] = download; + } else { + NOTREACHED(); // Should not exist! + return; + } + + // If the download already completed by the time we reached this point, then + // notify observers that it did. + PendingFinishedMap::iterator pending_it = + pending_finished_downloads_.find(info->download_id); + if (pending_it != pending_finished_downloads_.end()) + DownloadFinished(pending_it->first, pending_it->second); + + download->Rename(target_path); + + file_loop_->PostTask(FROM_HERE, + NewRunnableMethod(file_manager_, + &DownloadFileManager::OnFinalDownloadName, + download->id(), + target_path)); + + if (profile_->IsOffTheRecord()) { + // Fake a db handle for incognito mode, since nothing is actually stored in + // the database in this mode. We have to make sure that these handles don't + // collide with normal db handles, so we use a negative value. Eventually, + // they could overlap, but you'd have to do enough downloading that your ISP + // would likely stab you in the neck first. YMMV. + static int64 fake_db_handle = kUninitializedHandle - 1; + OnCreateDownloadEntryComplete(*info, fake_db_handle--); + } else { + // Update the history system with the new download. + // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) { + hs->CreateDownload( + *info, &cancelable_consumer_, + NewCallback(this, &DownloadManager::OnCreateDownloadEntryComplete)); + } + } +} + +// Convenience function for updating the history service for a download. +void DownloadManager::UpdateHistoryForDownload(DownloadItem* download) { + DCHECK(download); + + // Don't store info in the database if the download was initiated while in + // incognito mode or if it hasn't been initialized in our database table. + if (download->db_handle() <= kUninitializedHandle) + return; + + // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) { + hs->UpdateDownload(download->received_bytes(), + download->state(), + download->db_handle()); + } +} + +void DownloadManager::RemoveDownloadFromHistory(DownloadItem* download) { + DCHECK(download); + // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (download->db_handle() > kUninitializedHandle && hs) + hs->RemoveDownload(download->db_handle()); +} + +void DownloadManager::RemoveDownloadsFromHistoryBetween(const Time remove_begin, + const Time remove_end) { + // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->RemoveDownloadsBetween(remove_begin, remove_end); +} + +void DownloadManager::UpdateDownload(int32 download_id, int64 size) { + DownloadMap::iterator it = in_progress_.find(download_id); + if (it != in_progress_.end()) { + DownloadItem* download = it->second; + download->Update(size); + UpdateHistoryForDownload(download); + } +} + +void DownloadManager::DownloadFinished(int32 download_id, int64 size) { + DownloadMap::iterator it = in_progress_.find(download_id); + if (it != in_progress_.end()) { + // Remove the id from the list of pending ids. + PendingFinishedMap::iterator erase_it = + pending_finished_downloads_.find(download_id); + if (erase_it != pending_finished_downloads_.end()) + pending_finished_downloads_.erase(erase_it); + + DownloadItem* download = it->second; + download->Finished(size); + + // Open the download if the user or user prefs indicate it should be. + const std::wstring extension = + file_util::GetFileExtensionFromPath(download->full_path()); + if (download->open_when_complete() || ShouldOpenFileExtension(extension)) + OpenDownloadInShell(download, NULL); + + // Clean up will happen when the history system create callback runs if we + // don't have a valid db_handle yet. + if (download->db_handle() != kUninitializedHandle) { + in_progress_.erase(it); + NotifyAboutDownloadStop(); + UpdateHistoryForDownload(download); + } + } else { + // The download is done, but the user hasn't selected a final location for + // it yet (the Save As dialog box is probably still showing), so just keep + // track of the fact that this download id is complete, when the + // DownloadItem is constructed later we'll notify its completion then. + PendingFinishedMap::iterator erase_it = + pending_finished_downloads_.find(download_id); + DCHECK(erase_it == pending_finished_downloads_.end()); + pending_finished_downloads_[download_id] = size; + } +} + +// static +// We have to tell the ResourceDispatcherHost to cancel the download from this +// thread, since we can't forward tasks from the file thread to the io thread +// reliably (crash on shutdown race condition). +void DownloadManager::CancelDownloadRequest(int render_process_id, + int request_id) { + ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); + base::Thread* io_thread = g_browser_process->io_thread(); + if (!io_thread || !rdh) + return; + io_thread->message_loop()->PostTask(FROM_HERE, + NewRunnableFunction(&DownloadManager::OnCancelDownloadRequest, + rdh, + render_process_id, + request_id)); +} + +// static +void DownloadManager::OnCancelDownloadRequest(ResourceDispatcherHost* rdh, + int render_process_id, + int request_id) { + rdh->CancelRequest(render_process_id, request_id, false); +} + +void DownloadManager::DownloadCancelled(int32 download_id) { + DownloadMap::iterator it = in_progress_.find(download_id); + if (it == in_progress_.end()) + return; + DownloadItem* download = it->second; + + CancelDownloadRequest(download->render_process_id(), download->request_id()); + + // Clean up will happen when the history system create callback runs if we + // don't have a valid db_handle yet. + if (download->db_handle() != kUninitializedHandle) { + in_progress_.erase(it); + NotifyAboutDownloadStop(); + UpdateHistoryForDownload(download); + } + + // Tell the file manager to cancel the download. + file_manager_->RemoveDownload(download->id(), this); // On the UI thread + file_loop_->PostTask(FROM_HERE, + NewRunnableMethod(file_manager_, + &DownloadFileManager::CancelDownload, + download->id())); +} + +void DownloadManager::PauseDownload(int32 download_id, bool pause) { + DownloadMap::iterator it = in_progress_.find(download_id); + if (it != in_progress_.end()) { + DownloadItem* download = it->second; + if (pause == download->is_paused()) + return; + + // Inform the ResourceDispatcherHost of the new pause state. + base::Thread* io_thread = g_browser_process->io_thread(); + ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); + if (!io_thread || !rdh) + return; + + io_thread->message_loop()->PostTask(FROM_HERE, + NewRunnableFunction(&DownloadManager::OnPauseDownloadRequest, + rdh, + download->render_process_id(), + download->request_id(), + pause)); + } +} + +// static +void DownloadManager::OnPauseDownloadRequest(ResourceDispatcherHost* rdh, + int render_process_id, + int request_id, + bool pause) { + rdh->PauseRequest(render_process_id, request_id, pause); +} + +void DownloadManager::RemoveDownload(int64 download_handle) { + DownloadMap::iterator it = downloads_.find(download_handle); + if (it == downloads_.end()) + return; + + // Make history update. + DownloadItem* download = it->second; + RemoveDownloadFromHistory(download); + + // Remove from our tables and delete. + downloads_.erase(it); + delete download; + + // Tell observers to refresh their views. + FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); +} + +int DownloadManager::RemoveDownloadsBetween(const Time remove_begin, + const Time remove_end) { + RemoveDownloadsFromHistoryBetween(remove_begin, remove_end); + + int num_deleted = 0; + DownloadMap::iterator it = downloads_.begin(); + while (it != downloads_.end()) { + DownloadItem* download = it->second; + DownloadItem::DownloadState state = download->state(); + if (download->start_time() >= remove_begin && + (remove_end.is_null() || download->start_time() < remove_end) && + (state == DownloadItem::COMPLETE || + state == DownloadItem::CANCELLED)) { + // Remove from the map and move to the next in the list. + it = downloads_.erase(it); + delete download; + + ++num_deleted; + continue; + } + + ++it; + } + + // Tell observers to refresh their views. + if (num_deleted > 0) + FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); + + return num_deleted; +} + +int DownloadManager::RemoveDownloads(const Time remove_begin) { + return RemoveDownloadsBetween(remove_begin, Time()); +} + +// Initiate a download of a specific URL. We send the request to the +// ResourceDispatcherHost, and let it send us responses like a regular +// download. +void DownloadManager::DownloadUrl(const GURL& url, + const GURL& referrer, + WebContents* web_contents) { + DCHECK(web_contents); + file_manager_->DownloadUrl(url, + referrer, + web_contents->process()->host_id(), + web_contents->render_view_host()->routing_id(), + request_context_.get()); +} + +void DownloadManager::NotifyAboutDownloadStart() { + NotificationService::current()-> + Notify(NOTIFY_DOWNLOAD_START, NotificationService::AllSources(), + NotificationService::NoDetails()); +} + +void DownloadManager::NotifyAboutDownloadStop() { + NotificationService::current()-> + Notify(NOTIFY_DOWNLOAD_STOP, NotificationService::AllSources(), + NotificationService::NoDetails()); +} + +void DownloadManager::GenerateExtension(const std::wstring& file_name, + const std::string& mime_type, + std::wstring* generated_extension) { + // We're worried about three things here: + // + // 1) Security. Many sites let users upload content, such as buddy icons, to + // their web sites. We want to mitigate the case where an attacker + // supplies a malicious executable with an executable file extension but an + // honest site serves the content with a benign content type, such as + // image/jpeg. + // + // 2) Usability. If the site fails to provide a file extension, we want to + // guess a reasonable file extension based on the content type. + // + // 3) Shell integration. Some file extensions automatically integrate with + // the shell. We block these extensions to prevent a malicious web site + // from integrating with the user's shell. + + static const wchar_t default_extension[] = L"download"; + + // See if our file name already contains an extension. + std::wstring extension(file_util::GetFileExtensionFromPath(file_name)); + + // Rename shell-integrated extensions. + if (win_util::IsShellIntegratedExtension(extension)) + extension.assign(default_extension); + + std::string mime_type_from_extension; + net::GetMimeTypeFromFile(file_name, &mime_type_from_extension); + if (mime_type == mime_type_from_extension) { + // The hinted extension matches the mime type. It looks like a winner. + generated_extension->swap(extension); + return; + } + + if (IsExecutable(extension) && !IsExecutableMimeType(mime_type)) { + // We want to be careful about executable extensions. The worry here is + // that a trusted web site could be tricked into dropping an executable file + // on the user's filesystem. + if (!net::GetPreferredExtensionForMimeType(mime_type, &extension)) { + // We couldn't find a good extension for this content type. Use a dummy + // extension instead. + extension.assign(default_extension); + } + } + + if (extension.empty()) { + net::GetPreferredExtensionForMimeType(mime_type, &extension); + } else { + // Append entension generated from the mime type if: + // 1. New extension is not ".txt" + // 2. New extension is not the same as the already existing extension. + // 3. New extension is not executable. This action mitigates the case when + // an execuatable is hidden in a benign file extension; + // E.g. my-cat.jpg becomes my-cat.jpg.js if content type is + // application/x-javascript. + std::wstring append_extension; + if (net::GetPreferredExtensionForMimeType(mime_type, &append_extension)) { + if (append_extension != L".txt" && append_extension != extension && + !IsExecutable(append_extension)) + extension += append_extension; + } + } + + generated_extension->swap(extension); +} + +void DownloadManager::GenerateFilename(DownloadCreateInfo* info, + std::wstring* generated_name) { + std::wstring file_name = + net::GetSuggestedFilename(GURL(info->url), + info->content_disposition, + L"download"); + DCHECK(!file_name.empty()); + + // Make sure we get the right file extension. + std::wstring extension; + GenerateExtension(file_name, info->mime_type, &extension); + file_util::ReplaceExtension(&file_name, extension); + + // Prepend "_" to the file name if it's a reserved name + if (win_util::IsReservedName(file_name)) + file_name = std::wstring(L"_") + file_name; + + generated_name->assign(file_name); +} + +void DownloadManager::AddObserver(Observer* observer) { + observers_.AddObserver(observer); + observer->ModelChanged(); +} + +void DownloadManager::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +// Post Windows Shell operations to the Download thread, to avoid blocking the +// user interface. +void DownloadManager::ShowDownloadInShell(const DownloadItem* download) { + DCHECK(file_manager_); + file_loop_->PostTask(FROM_HERE, + NewRunnableMethod(file_manager_, + &DownloadFileManager::OnShowDownloadInShell, + download->full_path())); +} + +void DownloadManager::OpenDownloadInShell(const DownloadItem* download, + HWND parent_window) { + DCHECK(file_manager_); + file_loop_->PostTask(FROM_HERE, + NewRunnableMethod(file_manager_, + &DownloadFileManager::OnOpenDownloadInShell, + download->full_path(), download->url(), parent_window)); +} + +void DownloadManager::OpenFilesOfExtension(const std::wstring& extension, + bool open) { + if (open && !IsExecutable(extension)) + auto_open_.insert(extension); + else + auto_open_.erase(extension); + SaveAutoOpens(); +} + +bool DownloadManager::ShouldOpenFileExtension(const std::wstring& extension) { + if (!IsExecutable(extension) && + auto_open_.find(extension) != auto_open_.end()) + return true; + return false; +} + +// static +bool DownloadManager::IsExecutableMimeType(const std::string& mime_type) { + // JavaScript is just as powerful as EXE. + if (net::MatchesMimeType("text/javascript", mime_type)) + return true; + if (net::MatchesMimeType("text/javascript;version=*", mime_type)) + return true; + + // We don't consider other non-application types to be executable. + if (!net::MatchesMimeType("application/*", mime_type)) + return false; + + // These application types are not executable. + if (net::MatchesMimeType("application/*+xml", mime_type)) + return false; + if (net::MatchesMimeType("application/xml", mime_type)) + return false; + + return true; +} + +bool DownloadManager::IsExecutable(const std::wstring& extension) { + return exe_types_.find(extension) != exe_types_.end(); +} + +void DownloadManager::ResetAutoOpenFiles() { + auto_open_.clear(); + SaveAutoOpens(); +} + +bool DownloadManager::HasAutoOpenFileTypesRegistered() const { + return !auto_open_.empty(); +} + +void DownloadManager::SaveAutoOpens() { + PrefService* prefs = profile_->GetPrefs(); + if (prefs) { + std::wstring extensions; + for (std::set<std::wstring>::iterator it = auto_open_.begin(); + it != auto_open_.end(); ++it) { + extensions += *it + L":"; + } + if (!extensions.empty()) + extensions.erase(extensions.size() - 1); + prefs->SetString(prefs::kDownloadExtensionsToOpen, extensions); + } +} + +void DownloadManager::FileSelected(const std::wstring& path, void* params) { + DownloadCreateInfo* info = reinterpret_cast<DownloadCreateInfo*>(params); + if (*prompt_for_download_) + last_download_path_ = file_util::GetDirectoryFromPath(path); + ContinueStartDownload(info, path); +} + +void DownloadManager::FileSelectionCanceled(void* params) { + // The user didn't pick a place to save the file, so need to cancel the + // download that's already in progress to the temporary location. + DownloadCreateInfo* info = reinterpret_cast<DownloadCreateInfo*>(params); + file_loop_->PostTask(FROM_HERE, + NewRunnableMethod(file_manager_, &DownloadFileManager::CancelDownload, + info->download_id)); +} + +// Operations posted to us from the history service ---------------------------- + +// The history service has retrieved all download entries. 'entries' contains +// 'DownloadCreateInfo's in sorted order (by ascending start_time). +void DownloadManager::OnQueryDownloadEntriesComplete( + std::vector<DownloadCreateInfo>* entries) { + for (size_t i = 0; i < entries->size(); ++i) { + DownloadItem* download = new DownloadItem(entries->at(i)); + DCHECK(downloads_.find(download->db_handle()) == downloads_.end()); + downloads_[download->db_handle()] = download; + download->set_manager(this); + } + FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); +} + + +// Once the new DownloadItem's creation info has been committed to the history +// service, we associate the DownloadItem with the db handle, update our +// 'downloads_' map and inform observers. +void DownloadManager::OnCreateDownloadEntryComplete(DownloadCreateInfo info, + int64 db_handle) { + DownloadMap::iterator it = in_progress_.find(info.download_id); + DCHECK(it != in_progress_.end()); + + DownloadItem* download = it->second; + DCHECK(download->db_handle() == kUninitializedHandle); + download->set_db_handle(db_handle); + + // Insert into our full map. + DCHECK(downloads_.find(download->db_handle()) == downloads_.end()); + downloads_[download->db_handle()] = download; + + // The 'contents' may no longer exist if the user closed the tab before we get + // this start completion event. If it does, tell the origin WebContents to + // display its download shelf. + TabContents* contents = + tab_util::GetTabContentsByID(info.render_process_id, info.render_view_id); + + // If the contents no longer exists or is no longer active, we start the + // download in the last active browser. This is not ideal but better than + // fully hiding the download from the user. Note: non active means that the + // user navigated away from the tab contents. This has nothing to do with + // tab selection. + if (!contents || !contents->is_active()) { + Browser* last_active = BrowserList::GetLastActive(); + if (last_active) + contents = last_active->GetSelectedTabContents(); + } + + if (contents) + contents->OnStartDownload(download); + + // Inform interested objects about the new download. + FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); + NotifyAboutDownloadStart(); + + // If this download has been completed before we've received the db handle, + // post one final message to the history service so that it can be properly + // in sync with the DownloadItem's completion status, and also inform any + // observers so that they get more than just the start notification. + if (download->state() != DownloadItem::IN_PROGRESS) { + in_progress_.erase(it); + NotifyAboutDownloadStop(); + UpdateHistoryForDownload(download); + download->UpdateObservers(); + } +} + +// Called when the history service has retrieved the list of downloads that +// match the search text. +void DownloadManager::OnSearchComplete(HistoryService::Handle handle, + std::vector<int64>* results) { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + Observer* requestor = cancelable_consumer_.GetClientData(hs, handle); + if (!requestor) + return; + + std::vector<DownloadItem*> searched_downloads; + for (std::vector<int64>::iterator it = results->begin(); + it != results->end(); ++it) { + DownloadMap::iterator dit = downloads_.find(*it); + if (dit != downloads_.end()) + searched_downloads.push_back(dit->second); + } + + requestor->SetDownloads(searched_downloads); +} + diff --git a/chrome/browser/download/download_manager.h b/chrome/browser/download/download_manager.h new file mode 100644 index 0000000..0ab3dc1 --- /dev/null +++ b/chrome/browser/download/download_manager.h @@ -0,0 +1,483 @@ +// 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. +// +// The DownloadManager object manages the process of downloading, including +// updates to the history system and providing the information for displaying +// the downloads view in the Destinations tab. There is one DownloadManager per +// active profile in Chrome. +// +// Each download is represented by a DownloadItem, and all DownloadItems +// are owned by the DownloadManager which maintains a global list of all +// downloads. DownloadItems are created when a user initiates a download, +// and exist for the duration of the browser life time. +// +// Download observers: +// Objects that are interested in notifications about new downloads, or progress +// updates for a given download must implement one of the download observer +// interfaces: +// DownloadItem::Observer: +// - allows observers to receive notifications about one download from start +// to completion +// DownloadManager::Observer: +// - allows observers, primarily views, to be notified when changes to the +// set of all downloads (such as new downloads, or deletes) occur +// Use AddObserver() / RemoveObserver() on the appropriate download object to +// receive state updates. +// +// Download state persistence: +// The DownloadManager uses the history service for storing persistent +// information about the state of all downloads. The history system maintains a +// separate table for this called 'downloads'. At the point that the +// DownloadManager is constructed, we query the history service for the state of +// all persisted downloads. + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_H__ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_H__ + +#include <string> +#include <map> +#include <set> +#include <vector> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/observer_list.h" +#include "base/ref_counted.h" +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/history/download_types.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/shell_dialogs.h" +#include "chrome/common/pref_member.h" + +class DownloadFileManager; +class DownloadItem; +class DownloadItemView; +class DownloadManager; +class GURL; +class MessageLoop; +class PrefService; +class Profile; +class ResourceDispatcherHost; +class URLRequestContext; +class WebContents; + +namespace base { +class Thread; +} + +// DownloadItem ---------------------------------------------------------------- + +// One DownloadItem per download. This is the model class that stores all the +// state for a download. Multiple views, such as a tab's download shelf and the +// Destination tab's download view, may refer to a given DownloadItem. +class DownloadItem { + public: + enum DownloadState { + IN_PROGRESS, + COMPLETE, + CANCELLED, + REMOVING + }; + + // Interface that observers of a particular download must implement in order + // to receive updates to the download's status. + class Observer { + public: + virtual void OnDownloadUpdated(DownloadItem* download) = 0; + }; + + // Constructing from persistent store: + DownloadItem(const DownloadCreateInfo& info); + + // Constructing from user action: + DownloadItem(int32 download_id, + const std::wstring& path, + const std::wstring& url, + const Time start_time, + int64 download_size, + int render_process_id, + int request_id); + + ~DownloadItem(); + + void Init(bool start_timer); + + // Public API + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Notify our observers periodically + void UpdateObservers(); + + // Received a new chunk of data + void Update(int64 bytes_so_far); + + // Cancel the download operation. We need to distinguish between cancels at + // exit (DownloadManager destructor) from user interface initiated cancels + // because at exit, the history system may not exist, and any updates to it + // require AddRef'ing the DownloadManager in the destructor which results in + // a DCHECK failure. Set 'update_history' to false when canceling from at + // exit to prevent this crash. This may result in a difference between the + // downloaded file's size on disk, and what the history system's last record + // of it is. At worst, we'll end up re-downloading a small portion of the file + // when resuming a download (assuming the server supports byte ranges). + void Cancel(bool update_history); + + // Download operation completed + void Finished(int64 size); + + // The user wants to remove the download from the views and history. This + // operation does not delete the file on the disk. + void Remove(); + + // Start/stop sending periodic updates to our observers + void StartProgressTimer(); + void StopProgressTimer(); + + // Simple calculation of the amount of time remaining to completion. Fills + // |*remaining| with the amount of time remaining if successful. Fails and + // returns false if we do not have the number of bytes or the speed so can + // not estimate. + bool TimeRemaining(TimeDelta* remaining) const; + + // Simple speed estimate in bytes/s + int64 CurrentSpeed() const; + + // Rough percent complete, -1 means we don't know (since we didn't receive a + // total size). + int PercentComplete() const; + + // Update the download's path, the actual file is renamed on the download + // thread. + void Rename(const std::wstring& full_path); + + // Allow the user to temporarily pause a download or resume a paused download. + void TogglePause(); + + // Accessors + DownloadState state() const { return state_; } + std::wstring full_path() const { return full_path_; } + std::wstring file_name() const { return file_name_; } + std::wstring url() const { return url_; } + int64 total_bytes() const { return total_bytes_; } + void set_total_bytes(int64 total_bytes) { total_bytes_ = total_bytes; } + int64 received_bytes() const { return received_bytes_; } + int32 id() const { return id_; } + Time start_time() const { return start_time_; } + void set_db_handle(int64 handle) { db_handle_ = handle; } + int64 db_handle() const { return db_handle_; } + DownloadManager* manager() const { return manager_; } + void set_manager(DownloadManager* manager) { manager_ = manager; } + bool is_paused() const { return is_paused_; } + void set_is_paused(bool pause) { is_paused_ = pause; } + bool open_when_complete() const { return open_when_complete_; } + void set_open_when_complete(bool open) { open_when_complete_ = open; } + int render_process_id() const { return render_process_id_; } + int request_id() const { return request_id_; } + + private: + // Internal helper for maintaining consistent received and total sizes. + void UpdateSize(int64 size); + + // Request ID assigned by the ResourceDispatcherHost. + int32 id_; + + // Full path to the downloaded file + std::wstring full_path_; + + // Short display version of the file + std::wstring file_name_; + + // The URL from whence we came, for display + std::wstring url_; + + // Total bytes expected + int64 total_bytes_; + + // Current received bytes + int64 received_bytes_; + + // Start time for calculating remaining time + uintptr_t start_tick_; + + // The current state of this download + DownloadState state_; + + // The views of this item in the download shelf and download tab + ObserverList<Observer> observers_; + + // Time the download was started + Time start_time_; + + // Our persistent store handle + int64 db_handle_; + + // Timer for regularly updating our observers + base::RepeatingTimer<DownloadItem> update_timer_; + + // Our owning object + DownloadManager* manager_; + + // In progress downloads may be paused by the user, we note it here + bool is_paused_; + + // A flag for indicating if the download should be opened at completion. + bool open_when_complete_; + + // For canceling or pausing requests. + int render_process_id_; + int request_id_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadItem); +}; + + +// DownloadManager ------------------------------------------------------------- + +// Browser's download manager: manages all downloads and destination view. +class DownloadManager : public base::RefCountedThreadSafe<DownloadManager>, + public SelectFileDialog::Listener { + // For testing. + friend class DownloadManagerTest; + + public: + DownloadManager(); + ~DownloadManager(); + + static void RegisterUserPrefs(PrefService* prefs); + + // Interface to implement for observers that wish to be informed of changes + // to the DownloadManager's collection of downloads. + class Observer { + public: + // New or deleted download, observers should query us for the current set + // of downloads. + virtual void ModelChanged() = 0; + + // A callback once the DownloadManager has retrieved the requested set of + // downloads. The DownloadManagerObserver must copy the vector, but does not + // own the individual DownloadItems, when this call is made. + virtual void SetDownloads(std::vector<DownloadItem*>& downloads) = 0; + }; + + // Public API + + // Begin a search for all downloads matching 'search_text'. If 'search_text' + // is empty, return all known downloads. The results are returned in the + // 'SetDownloads' observer callback. + void GetDownloads(Observer* observer, + const std::wstring& search_text); + + // Returns true if initialized properly. + bool Init(Profile* profile); + + // Schedule a query of the history service to retrieve all downloads. + void QueryHistoryForDownloads(); + + // Notifications sent from the download thread to the UI thread + void StartDownload(DownloadCreateInfo* info); + void UpdateDownload(int32 download_id, int64 size); + void DownloadFinished(int32 download_id, int64 size); + + // Helper method for cancelling the network request associated with a + // download. + static void CancelDownloadRequest(int render_process_id, int request_id); + + // Called from a view when a user clicks a UI button or link. + void DownloadCancelled(int32 download_id); + void PauseDownload(int32 download_id, bool pause); + void RemoveDownload(int64 download_handle); + + // Remove downloads after remove_begin (inclusive) and before remove_end + // (exclusive). You may pass in null Time values to do an unbounded delete + // in either direction. + int RemoveDownloadsBetween(const Time remove_begin, const Time remove_end); + + // Remove downloads will delete all downloads that have a timestamp that is + // the same or more recent than |remove_begin|. The number of downloads + // deleted is returned back to the caller. + int RemoveDownloads(const Time remove_begin); + + // Download the object at the URL. Used in cases such as "Save Link As..." + void DownloadUrl(const GURL& url, + const GURL& referrer, + WebContents* web_contents); + + // Allow objects to observe the download creation process. + void AddObserver(Observer* observer); + + // Remove a download observer from ourself. + void RemoveObserver(Observer* observer); + + // Methods called on completion of a query sent to the history system. + void OnQueryDownloadEntriesComplete( + std::vector<DownloadCreateInfo>* entries); + void OnCreateDownloadEntryComplete(DownloadCreateInfo info, int64 db_handle); + void OnSearchComplete(HistoryService::Handle handle, + std::vector<int64>* results); + + // Show or Open a download via the Windows shell. + void ShowDownloadInShell(const DownloadItem* download); + void OpenDownloadInShell(const DownloadItem* download, HWND parent_window); + + // The number of in progress (including paused) downloads. + int in_progress_count() const { + return static_cast<int>(in_progress_.size()); + } + + std::wstring download_path() { return *download_path_; } + + // Registers this file extension for automatic opening upon download + // completion if 'open' is true, or prevents the extension from automatic + // opening if 'open' is false. + void OpenFilesOfExtension(const std::wstring& extension, bool open); + + // Tests if a file type should be opened automatically. + bool ShouldOpenFileExtension(const std::wstring& extension); + + // Tests if we think the server means for this mime_type to be executable. + static bool IsExecutableMimeType(const std::string& mime_type); + + // Tests if a file type is considered executable. + bool IsExecutable(const std::wstring& extension); + + // Resets the automatic open preference. + void ResetAutoOpenFiles(); + + // Returns true if there are automatic handlers registered for any file + // types. + bool HasAutoOpenFileTypesRegistered() const; + + // Overridden from SelectFileDialog::Listener: + virtual void FileSelected(const std::wstring& path, void* params); + virtual void FileSelectionCanceled(void* params); + + private: + // Shutdown the download manager. This call is needed only after Init. + void Shutdown(); + + // Called on the download thread to check whether the suggested file path + // exists. We don't check if the file exists on the UI thread to avoid UI + // stalls from interacting with the file system. + void CheckIfSuggestedPathExists(DownloadCreateInfo* info); + + // Called on the UI thread once the DownloadManager has determined whether the + // suggested file path exists. + void OnPathExistenceAvailable(DownloadCreateInfo* info); + + // Called back after a target path for the file to be downloaded to has been + // determined, either automatically based on the suggested file name, or by + // the user in a Save As dialog box. + void ContinueStartDownload(DownloadCreateInfo* info, + const std::wstring& target_path); + + // Update the history service for a particular download. + void UpdateHistoryForDownload(DownloadItem* download); + void RemoveDownloadFromHistory(DownloadItem* download); + void RemoveDownloadsFromHistoryBetween(const Time remove_begin, + const Time remove_before); + + // Inform the notification service of download starts and stops. + void NotifyAboutDownloadStart(); + void NotifyAboutDownloadStop(); + + // Create an extension based on the file name and mime type. + void GenerateExtension(const std::wstring& file_name, + const std::string& mime_type, + std::wstring* generated_extension); + + // Create a file name based on the response from the server. + void GenerateFilename(DownloadCreateInfo* info, std::wstring* generated_name); + + // Persist the automatic opening preference. + void SaveAutoOpens(); + + // Runs the network cancel on the IO thread. + static void OnCancelDownloadRequest(ResourceDispatcherHost* rdh, + int render_process_id, + int request_id); + + // Runs the pause on the IO thread. + static void OnPauseDownloadRequest(ResourceDispatcherHost* rdh, + int render_process_id, + int request_id, + bool pause); + + // 'downloads_' is map of all downloads in this profile. The key is the handle + // returned by the history system, which is unique across sessions. This map + // owns all the DownloadItems once they have been created in the history + // system. + // + // 'in_progress_' is a map of all downloads that are in progress and that have + // not yet received a valid history handle. The key is the ID assigned by the + // ResourceDispatcherHost, which is unique for the current session. This map + // does not own the DownloadItems. + // + // When a download is created through a user action, the corresponding + // DownloadItem* is placed in 'in_progress_' and remains there until it has + // received a valid handle from the history system. Once it has a valid + // handle, the DownloadItem* is placed in the 'downloads_' map. When the + // download is complete, it is removed from 'in_progress_'. Downloads from + // past sessions read from a persisted state from the history system are + // placed directly into 'downloads_' since they have valid handles in the + // history system. + typedef base::hash_map<int64, DownloadItem*> DownloadMap; + DownloadMap downloads_; + DownloadMap in_progress_; + + // True if the download manager has been initialized and requires a shutdown. + bool shutdown_needed_; + + // Observers that want to be notified of changes to the set of downloads. + ObserverList<Observer> observers_; + + // The current active profile. + Profile* profile_; + scoped_refptr<URLRequestContext> request_context_; + + // Used for history service request management. + CancelableRequestConsumerT<Observer*, 0> cancelable_consumer_; + + // Non-owning pointer for handling file writing on the download_thread_. + DownloadFileManager* file_manager_; + + // A pointer to the main UI loop. + MessageLoop* ui_loop_; + + // A pointer to the file thread's loop. The file thread lives longer than + // the DownloadManager, so this is safe to cache. + MessageLoop* file_loop_; + + // User preferences + BooleanPrefMember prompt_for_download_; + StringPrefMember download_path_; + + // The user's last choice for download directory. This is only used when the + // user wants us to prompt for a save location for each download. + std::wstring last_download_path_; + + // Set of file extensions to open at download completion. + std::set<std::wstring> auto_open_; + + // Set of file extensions that are executables and shouldn't be auto opened. + std::set<std::wstring> exe_types_; + + // Keep track of downloads that are completed before the user selects the + // destination, so that observers are appropriately notified of completion + // after this determination is made. + // The map is of download_id->remaining size (bytes), both of which are + // required when calling DownloadFinished. + typedef std::map<int32, int64> PendingFinishedMap; + PendingFinishedMap pending_finished_downloads_; + + // The "Save As" dialog box used to ask the user where a file should be + // saved. + scoped_refptr<SelectFileDialog> select_file_dialog_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadManager); +}; + + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_H__ diff --git a/chrome/browser/download/download_manager_unittest.cc b/chrome/browser/download/download_manager_unittest.cc new file mode 100644 index 0000000..abfc100 --- /dev/null +++ b/chrome/browser/download/download_manager_unittest.cc @@ -0,0 +1,320 @@ +// 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 <string> + +#include "base/logging.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/browser/download/download_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +class DownloadManagerTest : public testing::Test { + public: + DownloadManagerTest() { + download_manager_ = new DownloadManager(); + download_util::InitializeExeTypes(&download_manager_->exe_types_); + } + + void GetGeneratedFilename(const std::string& content_disposition, + const std::wstring& url, + const std::string& mime_type, + std::wstring* generated_name) { + DownloadCreateInfo info; + info.content_disposition = content_disposition; + info.url = url; + info.mime_type = mime_type; + download_manager_->GenerateFilename(&info, generated_name); + } + + protected: + scoped_refptr<DownloadManager> download_manager_; + MessageLoopForUI message_loop_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadManagerTest); +}; + +static const struct { + const char* disposition; + const wchar_t* url; + const char* mime_type; + const wchar_t* expected_name; +} kGeneratedFiles[] = { + // No 'filename' keyword in the disposition, use the URL + {"a_file_name.txt", + L"http://www.evil.com/my_download.txt", + "text/plain", + L"my_download.txt"}, + + // Disposition has relative paths, remove them + {"filename=../../../../././../a_file_name.txt", + L"http://www.evil.com/my_download.txt", + "text/plain", + L"a_file_name.txt"}, + + // Disposition has parent directories, remove them + {"filename=dir1/dir2/a_file_name.txt", + L"http://www.evil.com/my_download.txt", + "text/plain", + L"a_file_name.txt"}, + + // No useful information in disposition or URL, use default + {"", L"http://www.truncated.com/path/", "text/plain", L"download.txt"}, + + // Spaces in the disposition file name + {"filename=My Downloaded File.exe", + L"http://www.frontpagehacker.com/a_download.exe", + "application/octet-stream", + L"My Downloaded File.exe"}, + + {"filename=my-cat", + L"http://www.example.com/my-cat", + "image/jpeg", + L"my-cat.jpg"}, + + {"filename=my-cat", + L"http://www.example.com/my-cat", + "text/plain", + L"my-cat.txt"}, + + {"filename=my-cat", + L"http://www.example.com/my-cat", + "text/html", + L"my-cat.htm"}, + + {"filename=my-cat", + L"http://www.example.com/my-cat", + "dance/party", + L"my-cat"}, + + {"filename=my-cat.jpg", + L"http://www.example.com/my-cat.jpg", + "text/plain", + L"my-cat.jpg"}, + + {"filename=evil.exe", + L"http://www.goodguy.com/evil.exe", + "image/jpeg", + L"evil.jpg"}, + + {"filename=evil.exe.exe", + L"http://www.goodguy.com/evil.exe.exe", + "dance/party", + L"evil.exe.download"}, + + {"filename=evil.exe", + L"http://www.goodguy.com/evil.exe", + "application/xml", + L"evil.xml"}, + + {"filename=evil.exe", + L"http://www.goodguy.com/evil.exe", + "application/html+xml", + L"evil.download"}, + + {"filename=evil.exe", + L"http://www.goodguy.com/evil.exe", + "application/rss+xml", + L"evil.download"}, + + {"filename=utils.js", + L"http://www.goodguy.com/utils.js", + "application/x-javascript", + L"utils.js"}, + + {"filename=contacts.js", + L"http://www.goodguy.com/contacts.js", + "application/json", + L"contacts.js"}, + + {"filename=utils.js", + L"http://www.goodguy.com/utils.js", + "text/javascript", + L"utils.js"}, + + {"filename=utils.js", + L"http://www.goodguy.com/utils.js", + "text/javascript;version=2", + L"utils.js"}, + + {"filename=utils.js", + L"http://www.goodguy.com/utils.js", + "application/ecmascript", + L"utils.js"}, + + {"filename=utils.js", + L"http://www.goodguy.com/utils.js", + "application/ecmascript;version=4", + L"utils.js"}, + + {"filename=program.exe", + L"http://www.goodguy.com/program.exe", + "application/foo-bar", + L"program.exe"}, + + {"filename=../foo.txt", + L"http://www.evil.com/../foo.txt", + "text/plain", + L"foo.txt"}, + + {"filename=..\\foo.txt", + L"http://www.evil.com/..\\foo.txt", + "text/plain", + L"foo.txt"}, + + {"filename=.hidden", + L"http://www.evil.com/.hidden", + "text/plain", + L"hidden.txt"}, + + {"filename=trailing.", + L"http://www.evil.com/trailing.", + "dance/party", + L"trailing"}, + + {"filename=trailing.", + L"http://www.evil.com/trailing.", + "text/plain", + L"trailing.txt"}, + + {"filename=.", + L"http://www.evil.com/.", + "dance/party", + L"download"}, + + {"filename=..", + L"http://www.evil.com/..", + "dance/party", + L"download"}, + + {"filename=...", + L"http://www.evil.com/...", + "dance/party", + L"download"}, + + {"a_file_name.txt", + L"http://www.evil.com/", + "image/jpeg", + L"download.jpg"}, + + {"filename=", + L"http://www.evil.com/", + "image/jpeg", + L"download.jpg"}, + + {"filename=simple", + L"http://www.example.com/simple", + "application/octet-stream", + L"simple"}, + + {"filename=COM1", + L"http://www.goodguy.com/COM1", + "application/foo-bar", + L"_COM1"}, + + {"filename=COM4.txt", + L"http://www.goodguy.com/COM4.txt", + "text/plain", + L"_COM4.txt"}, + + {"filename=lpt1.TXT", + L"http://www.goodguy.com/lpt1.TXT", + "text/plain", + L"_lpt1.TXT"}, + + {"filename=clock$.txt", + L"http://www.goodguy.com/clock$.txt", + "text/plain", + L"_clock$.txt"}, + + {"filename=mycom1.foo", + L"http://www.goodguy.com/mycom1.foo", + "text/plain", + L"mycom1.foo"}, + + {"filename=Setup.exe.local", + L"http://www.badguy.com/Setup.exe.local", + "application/foo-bar", + L"Setup.exe.download"}, + + {"filename=Setup.exe.local.local", + L"http://www.badguy.com/Setup.exe.local", + "application/foo-bar", + L"Setup.exe.local.download"}, + + {"filename=Setup.exe.lnk", + L"http://www.badguy.com/Setup.exe.lnk", + "application/foo-bar", + L"Setup.exe.download"}, + + {"filename=Desktop.ini", + L"http://www.badguy.com/Desktop.ini", + "application/foo-bar", + L"_Desktop.ini"}, + + {"filename=Thumbs.db", + L"http://www.badguy.com/Thumbs.db", + "application/foo-bar", + L"_Thumbs.db"}, + + {"filename=source.srf", + L"http://www.hotmail.com", + "image/jpeg", + L"source.srf.jpg"}, + + {"filename=source.jpg", + L"http://www.hotmail.com", + "application/x-javascript", + L"source.jpg"}, + + // NetUtilTest.{GetSuggestedFilename, GetFileNameFromCD} test these + // more thoroughly. Tested below are a small set of samples. + {"attachment; filename=\"%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg\"", + L"http://www.examples.com/", + "image/jpeg", + L"\uc608\uc220 \uc608\uc220.jpg"}, + + {"attachment; name=abc de.pdf", + L"http://www.examples.com/q.cgi?id=abc", + "application/octet-stream", + L"abc de.pdf"}, + + {"filename=\"=?EUC-JP?Q?=B7=DD=BD=D13=2Epng?=\"", + L"http://www.example.com/path", + "image/png", + L"\x82b8\x8853" L"3.png"}, + + // The following two have invalid CD headers and filenames come + // from the URL. + {"attachment; filename==?iiso88591?Q?caf=EG?=", + L"http://www.example.com/test%20123", + "image/jpeg", + L"test 123.jpg"}, + + {"malformed_disposition", + L"http://www.google.com/%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg", + "image/jpeg", + L"\uc608\uc220 \uc608\uc220.jpg"}, + + // Invalid C-D. No filename from URL. Falls back to 'download'. + {"attachment; filename==?iso88591?Q?caf=E3?", + L"http://www.google.com/path1/path2/", + "image/jpeg", + L"download.jpg"}, + + // TODO(darin): Add some raw 8-bit Content-Disposition tests. +}; + +// Tests to ensure that the file names we generate from hints from the server +// (content-disposition, URL name, etc) don't cause security holes. +TEST_F(DownloadManagerTest, TestDownloadFilename) { + for (int i = 0; i < arraysize(kGeneratedFiles); ++i) { + std::wstring file_name; + GetGeneratedFilename(kGeneratedFiles[i].disposition, + kGeneratedFiles[i].url, + kGeneratedFiles[i].mime_type, + &file_name); + EXPECT_EQ(kGeneratedFiles[i].expected_name, file_name); + } +} + diff --git a/chrome/browser/download/download_uitest.cc b/chrome/browser/download/download_uitest.cc new file mode 100644 index 0000000..e6c071e --- /dev/null +++ b/chrome/browser/download/download_uitest.cc @@ -0,0 +1,241 @@ +// 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 <shlwapi.h> +#include <sstream> +#include <string> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "chrome/browser/automation/url_request_mock_http_job.h" +#include "chrome/browser/automation/url_request_slow_download_job.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/ui/ui_test.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "net/url_request/url_request_unittest.h" + +using std::wstring; + +namespace { + +const wchar_t kDocRoot[] = L"chrome/test/data"; + +// Checks if the volume supports Alternate Data Streams. This is required for +// the Zone Identifier implementation. +bool VolumeSupportsADS(const std::wstring path) { + wchar_t drive[MAX_PATH] = {0}; + wcscpy_s(drive, MAX_PATH, path.c_str()); + + EXPECT_TRUE(PathStripToRootW(drive)); + + DWORD fs_flags = 0; + EXPECT_TRUE(GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL, + 0)); + + if (fs_flags & FILE_NAMED_STREAMS) + return true; + + return false; +} + +// Checks if the ZoneIdentifier is correctly set to "Internet" (3) +void CheckZoneIdentifier(const std::wstring full_path) { + const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + + std::wstring path = full_path + L":Zone.Identifier"; + HANDLE file = CreateFile(path.c_str(), GENERIC_READ, kShare, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + ASSERT_TRUE(INVALID_HANDLE_VALUE != file); + + char buffer[100] = {0}; + DWORD read = 0; + ASSERT_TRUE(ReadFile(file, buffer, 100, &read, NULL)); + CloseHandle(file); + + const char kIdentifier[] = "[ZoneTransfer]\nZoneId=3"; + ASSERT_EQ(arraysize(kIdentifier), read); + + ASSERT_EQ(0, strcmp(kIdentifier, buffer)); +} + +class DownloadTest : public UITest { + protected: + DownloadTest() : UITest() {} + + void CleanUpDownload(const std::wstring& client_filename, + const std::wstring& server_filename) { + // Find the path on the client. + std::wstring file_on_client(download_prefix_); + file_on_client.append(client_filename); + EXPECT_PRED1(file_util::PathExists, file_on_client); + + // Find the path on the server. + std::wstring file_on_server; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, + &file_on_server)); + file_on_server.append(L"\\"); + file_on_server.append(server_filename); + ASSERT_TRUE(file_util::PathExists(file_on_server)); + + // Check that we downloaded the file correctly. + EXPECT_PRED2(file_util::ContentsEqual, file_on_server, file_on_client); + + // Check if the Zone Identifier is correclty set. + if (VolumeSupportsADS(file_on_client)) + CheckZoneIdentifier(file_on_client); + + // Delete the client copy of the file. + EXPECT_PRED2(file_util::Delete, file_on_client, false); + } + + void CleanUpDownload(const std::wstring& file) { + CleanUpDownload(file, file); + } + + virtual void SetUp() { + UITest::SetUp(); + download_prefix_ = GetDownloadDirectory(); + download_prefix_ += file_util::kPathSeparator; + } + + protected: + void RunSizeTest(const wstring& url, + const wstring& expected_title_in_progress, + const wstring& expected_title_finished) { + { + EXPECT_EQ(1, GetTabCount()); + + NavigateToURL(GURL(url)); + // Downloads appear in the shelf + WaitUntilTabCount(1); + // TODO(tc): check download status text + + // Complete sending the request. We do this by loading a second URL in a + // separate tab. + scoped_ptr<BrowserProxy> window(automation()->GetBrowserWindow(0)); + EXPECT_TRUE(window->AppendTab(GURL( + URLRequestSlowDownloadJob::kFinishDownloadUrl))); + EXPECT_EQ(2, GetTabCount()); + // TODO(tc): check download status text + + // Make sure the download shelf is showing. + scoped_ptr<TabProxy> dl_tab(window->GetTab(0)); + ASSERT_TRUE(dl_tab.get()); + EXPECT_TRUE(WaitForDownloadShelfVisible(dl_tab.get())); + } + + std::wstring filename = file_util::GetFilenameFromPath(url); + EXPECT_PRED1(file_util::PathExists, download_prefix_ + filename); + + // Delete the file we just downloaded. + for (int i = 0; i < 10; ++i) { + if (file_util::Delete(download_prefix_ + filename, false)) + break; + Sleep(kWaitForActionMaxMsec / 10); + } + EXPECT_FALSE(file_util::PathExists(download_prefix_ + filename)); + } + + wstring download_prefix_; +}; + +} // namespace + +// Download a file with non-viewable content, verify that the +// download tab opened and the file exists. +TEST_F(DownloadTest, DownloadMimeType) { + wstring file = L"download-test1.lib"; + wstring expected_title = L"100% - " + file; + + EXPECT_EQ(1, GetTabCount()); + + NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file)); + // No new tabs created, downloads appear in the current tab's download shelf. + WaitUntilTabCount(1); + + // Wait until the file is downloaded. + Sleep(1000); + + CleanUpDownload(file); + + scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + EXPECT_TRUE(WaitForDownloadShelfVisible(tab_proxy.get())); +} + +// Access a file with a viewable mime-type, verify that a download +// did not initiate. +TEST_F(DownloadTest, NoDownload) { + wstring file = L"download-test2.html"; + wstring file_path = download_prefix_; + file_util::AppendToPath(&file_path, file); + + if (file_util::PathExists(file_path)) + ASSERT_TRUE(file_util::Delete(file_path, false)); + + EXPECT_EQ(1, GetTabCount()); + + NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file)); + WaitUntilTabCount(1); + + // Wait to see if the file will be downloaded. + Sleep(1000); + + EXPECT_FALSE(file_util::PathExists(file_path)); + if (file_util::PathExists(file_path)) + ASSERT_TRUE(file_util::Delete(file_path, false)); + + scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + EXPECT_FALSE(WaitForDownloadShelfVisible(tab_proxy.get())); +} + +// Download a 0-size file with a content-disposition header, verify that the +// download tab opened and the file exists as the filename specified in the +// header. This also ensures we properly handle empty file downloads. +TEST_F(DownloadTest, ContentDisposition) { + wstring file = L"download-test3.html"; + wstring download_file = L"download-test3-attachment.html"; + wstring expected_title = L"100% - " + download_file; + + EXPECT_EQ(1, GetTabCount()); + + NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file)); + WaitUntilTabCount(1); + + // Wait until the file is downloaded. + Sleep(1000); + + CleanUpDownload(download_file, file); + + // Ensure the download shelf is visible on the current tab. + scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + EXPECT_TRUE(WaitForDownloadShelfVisible(tab_proxy.get())); +} + +// UnknownSize and KnownSize are tests which depend on +// URLRequestSlowDownloadJob to serve content in a certain way. Data will be +// sent in two chunks where the first chunk is 35K and the second chunk is 10K. +// The test will first attempt to download a file; but the server will "pause" +// in the middle until the server receives a second request for +// "download-finish. At that time, the download will finish. +TEST_F(DownloadTest, UnknownSize) { + std::wstring url(URLRequestSlowDownloadJob::kUnknownSizeUrl); + std::wstring filename = file_util::GetFilenameFromPath(url); + RunSizeTest(url, L"32.0 KB - " + filename, L"100% - " + filename); +} + +// http://b/1158253 +TEST_F(DownloadTest, DISABLED_KnownSize) { + std::wstring url(URLRequestSlowDownloadJob::kKnownSizeUrl); + std::wstring filename = file_util::GetFilenameFromPath(url); + RunSizeTest(url, L"71% - " + filename, L"100% - " + filename); +} + diff --git a/chrome/browser/download/download_util.cc b/chrome/browser/download/download_util.cc new file mode 100644 index 0000000..530eade --- /dev/null +++ b/chrome/browser/download/download_util.cc @@ -0,0 +1,407 @@ +// 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. +// +// Download utility implementation + +#include <string> + +#include "chrome/browser/download/download_util.h" + +#include "base/base_drag_source.h" +#include "base/file_util.h" +#include "base/gfx/image_operations.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/common/clipboard_service.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/view.h" +#include "generated_resources.h" +#include "SkPath.h" +#include "SkShader.h" + +namespace download_util { + +// BaseContextMenu ------------------------------------------------------------- + +BaseContextMenu::BaseContextMenu(DownloadItem* download) : download_(download) { +} + +BaseContextMenu::~BaseContextMenu() { +} + +// How many times to cycle the complete animation. This should be an odd number +// so that the animation ends faded out. +static const int kCompleteAnimationCycles = 5; + +bool BaseContextMenu::IsItemChecked(int id) const { + switch (id) { + case OPEN_WHEN_COMPLETE: + return download_->open_when_complete(); + case ALWAYS_OPEN_TYPE: { + const std::wstring extension = + file_util::GetFileExtensionFromPath(download_->full_path()); + return download_->manager()->ShouldOpenFileExtension(extension); + } + } + return false; +} + +bool BaseContextMenu::IsItemDefault(int id) const { + return false; +} + +std::wstring BaseContextMenu::GetLabel(int id) const { + switch (id) { + case SHOW_IN_FOLDER: + return l10n_util::GetString(IDS_DOWNLOAD_LINK_SHOW); + case COPY_LINK: + return l10n_util::GetString(IDS_CONTENT_CONTEXT_COPYLINKLOCATION); + case COPY_PATH: + return l10n_util::GetString(IDS_DOWNLOAD_MENU_COPY_PATH); + case COPY_FILE: + return l10n_util::GetString(IDS_DOWNLOAD_MENU_COPY_FILE); + case OPEN_WHEN_COMPLETE: + if (download_->state() == DownloadItem::IN_PROGRESS) + return l10n_util::GetString(IDS_DOWNLOAD_MENU_OPEN_WHEN_COMPLETE); + return l10n_util::GetString(IDS_DOWNLOAD_MENU_OPEN); + case ALWAYS_OPEN_TYPE: + return l10n_util::GetString(IDS_DOWNLOAD_MENU_ALWAYS_OPEN_TYPE); + case REMOVE_ITEM: + return l10n_util::GetString(IDS_DOWNLOAD_MENU_REMOVE_ITEM); + case CANCEL: + return l10n_util::GetString(IDS_DOWNLOAD_MENU_CANCEL); + default: + NOTREACHED(); + } + return std::wstring(); +} + +bool BaseContextMenu::SupportsCommand(int id) const { + return id > 0 && id < MENU_LAST; +} + +bool BaseContextMenu::IsCommandEnabled(int id) const { + switch (id) { + case SHOW_IN_FOLDER: + case COPY_PATH: + case COPY_FILE: + case OPEN_WHEN_COMPLETE: + return download_->state() != DownloadItem::CANCELLED; + case ALWAYS_OPEN_TYPE: + return CanOpenDownload(download_); + case CANCEL: + return download_->state() == DownloadItem::IN_PROGRESS; + default: + return id > 0 && id < MENU_LAST; + } +} + +void BaseContextMenu::ExecuteCommand(int id) { + ClipboardService* clipboard = g_browser_process->clipboard_service(); + DCHECK(clipboard); + switch (id) { + case SHOW_IN_FOLDER: + download_->manager()->ShowDownloadInShell(download_); + break; + case COPY_LINK: + clipboard->Clear(); + clipboard->WriteText(download_->url()); + break; + case COPY_PATH: + clipboard->Clear(); + clipboard->WriteText(download_->full_path()); + break; + case COPY_FILE: + // TODO(paulg): Move to OSExchangeData when implementing drag and drop? + clipboard->Clear(); + clipboard->WriteFile(download_->full_path()); + break; + case OPEN_WHEN_COMPLETE: + OpenDownload(download_); + break; + case ALWAYS_OPEN_TYPE: { + const std::wstring extension = + file_util::GetFileExtensionFromPath(download_->full_path()); + download_->manager()->OpenFilesOfExtension( + extension, !IsItemChecked(ALWAYS_OPEN_TYPE)); + break; + } + case REMOVE_ITEM: + download_->Remove(); + break; + case CANCEL: + download_->Cancel(true); + break; + default: + NOTREACHED(); + } +} + +// DownloadShelfContextMenu ---------------------------------------------------- + +DownloadShelfContextMenu::DownloadShelfContextMenu( + DownloadItem* download, + HWND window, + DownloadItemView::BaseDownloadItemModel* model, + const CPoint& point) + : BaseContextMenu(download), + model_(model) { + DCHECK(model_); + + // The menu's anchor point is determined based on the UI layout. + Menu::AnchorPoint anchor_point; + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + anchor_point = Menu::TOPRIGHT; + else + anchor_point = Menu::TOPLEFT; + + Menu context_menu(this, anchor_point, window); + if (download->state() == DownloadItem::COMPLETE) + context_menu.AppendMenuItem(OPEN_WHEN_COMPLETE, L"", Menu::NORMAL); + else + context_menu.AppendMenuItem(OPEN_WHEN_COMPLETE, L"", Menu::CHECKBOX); + context_menu.AppendMenuItem(ALWAYS_OPEN_TYPE, L"", Menu::CHECKBOX); + context_menu.AppendSeparator(); + context_menu.AppendMenuItem(SHOW_IN_FOLDER, L"", Menu::NORMAL); + context_menu.AppendSeparator(); + context_menu.AppendMenuItem(CANCEL, L"", Menu::NORMAL); + context_menu.RunMenuAt(point.x, point.y); +} + +DownloadShelfContextMenu::~DownloadShelfContextMenu() { +} + +bool DownloadShelfContextMenu::IsItemDefault(int id) const { + return id == OPEN_WHEN_COMPLETE; +} + +void DownloadShelfContextMenu::ExecuteCommand(int id) { + if (id == CANCEL) + model_->CancelTask(); + else + BaseContextMenu::ExecuteCommand(id); +} + +// DownloadDestinationContextMenu ---------------------------------------------- + +DownloadDestinationContextMenu::DownloadDestinationContextMenu( + DownloadItem* download, + HWND window, + const CPoint& point) + : BaseContextMenu(download) { + // The menu's anchor point is determined based on the UI layout. + Menu::AnchorPoint anchor_point; + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + anchor_point = Menu::TOPRIGHT; + else + anchor_point = Menu::TOPLEFT; + + Menu context_menu(this, anchor_point, window); + context_menu.AppendMenuItem(SHOW_IN_FOLDER, L"", Menu::NORMAL); + context_menu.AppendSeparator(); + context_menu.AppendMenuItem(COPY_LINK, L"", Menu::NORMAL); + context_menu.AppendMenuItem(COPY_PATH, L"", Menu::NORMAL); + context_menu.AppendMenuItem(COPY_FILE, L"", Menu::NORMAL); + context_menu.AppendSeparator(); + context_menu.AppendMenuItem(OPEN_WHEN_COMPLETE, L"", Menu::CHECKBOX); + context_menu.AppendMenuItem(ALWAYS_OPEN_TYPE, L"", Menu::CHECKBOX); + context_menu.AppendSeparator(); + context_menu.AppendMenuItem(REMOVE_ITEM, L"", Menu::NORMAL); + context_menu.RunMenuAt(point.x, point.y); +} + +DownloadDestinationContextMenu::~DownloadDestinationContextMenu() { +} + +// Download opening ------------------------------------------------------------ + +bool CanOpenDownload(DownloadItem* download) { + const std::wstring extension = + file_util::GetFileExtensionFromPath(download->full_path()); + return !download->manager()->IsExecutable(extension); +} + +void OpenDownload(DownloadItem* download) { + if (download->state() == DownloadItem::IN_PROGRESS) + download->set_open_when_complete(!download->open_when_complete()); + else if (download->state() == DownloadItem::COMPLETE) + download->manager()->OpenDownloadInShell(download, NULL); +} + +// Download progress painting -------------------------------------------------- + +// Common bitmaps used for download progress animations. We load them once the +// first time we do a progress paint, then reuse them as they are always the +// same. +SkBitmap* g_foreground_16 = NULL; +SkBitmap* g_background_16 = NULL; +SkBitmap* g_foreground_32 = NULL; +SkBitmap* g_background_32 = NULL; + +void PaintDownloadProgress(ChromeCanvas* canvas, + ChromeViews::View* containing_view, + int origin_x, + int origin_y, + int start_angle, + int percent_done, + PaintDownloadProgressSize size) { + DCHECK(containing_view); + + // Load up our common bitmaps + if (!g_background_16) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + g_foreground_16 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); + g_background_16 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_16); + g_foreground_32 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); + g_background_32 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_32); + } + + SkBitmap* background = (size == BIG) ? g_background_32 : g_background_16; + SkBitmap* foreground = (size == BIG) ? g_foreground_32 : g_foreground_16; + + const int kProgressIconSize = (size == BIG) ? kBigProgressIconSize : + kSmallProgressIconSize; + + int height = background->height(); + + // We start by storing the bounds of the background and foreground bitmaps + // so that it is easy to mirror the bounds if the UI layout is RTL. + gfx::Rect background_bounds(origin_x, origin_y, + background->width(), background->height()); + gfx::Rect foreground_bounds(origin_x, origin_y, + foreground->width(), foreground->height()); + + // Mirror the positions if necessary. + int mirrored_x = containing_view->MirroredLeftPointForRect(background_bounds); + background_bounds.set_x(mirrored_x); + mirrored_x = containing_view->MirroredLeftPointForRect(foreground_bounds); + foreground_bounds.set_x(mirrored_x); + + // Draw the background progress image. + SkPaint background_paint; + canvas->DrawBitmapInt(*background, + background_bounds.x(), + background_bounds.y(), + background_paint); + + // Layer the foreground progress image in an arc proportional to the download + // progress. The arc grows clockwise, starting in the midnight position, as + // the download progresses. However, if the download does not have known total + // size (the server didn't give us one), then we just spin an arc around until + // we're done. + float sweep_angle = 0.0; + float start_pos = static_cast<float>(kStartAngleDegrees); + if (percent_done < 0) { + sweep_angle = kUnknownAngleDegrees; + start_pos = static_cast<float>(start_angle); + } else if (percent_done > 0) { + sweep_angle = static_cast<float>(kMaxDegrees / 100.0 * percent_done); + } + + // Set up an arc clipping region for the foreground image. Don't bother using + // a clipping region if it would round to 360 (really 0) degrees, since that + // would eliminate the foreground completely and be quite confusing (it would + // look like 0% complete when it should be almost 100%). + SkPaint foreground_paint; + if (sweep_angle < static_cast<float>(kMaxDegrees - 1)) { + SkRect oval; + oval.set(SkIntToScalar(foreground_bounds.x()), + SkIntToScalar(foreground_bounds.y()), + SkIntToScalar(foreground_bounds.x() + kProgressIconSize), + SkIntToScalar(foreground_bounds.y() + kProgressIconSize)); + SkPath path; + path.arcTo(oval, + SkFloatToScalar(start_pos), + SkFloatToScalar(sweep_angle), false); + path.lineTo(SkIntToScalar(foreground_bounds.x() + kProgressIconSize / 2), + SkIntToScalar(foreground_bounds.y() + kProgressIconSize / 2)); + + SkShader* shader = + SkShader::CreateBitmapShader(*foreground, + SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode); + SkMatrix shader_scale; + shader_scale.setTranslate(SkIntToScalar(foreground_bounds.x()), + SkIntToScalar(foreground_bounds.y())); + shader->setLocalMatrix(shader_scale); + foreground_paint.setShader(shader); + foreground_paint.setAntiAlias(true); + shader->unref(); + canvas->drawPath(path, foreground_paint); + return; + } + + canvas->DrawBitmapInt(*foreground, + foreground_bounds.x(), + foreground_bounds.y(), + foreground_paint); +} + +void PaintDownloadComplete(ChromeCanvas* canvas, + ChromeViews::View* containing_view, + int origin_x, + int origin_y, + double animation_progress, + PaintDownloadProgressSize size) { + DCHECK(containing_view); + + // Load up our common bitmaps. + if (!g_foreground_16) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + g_foreground_16 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); + g_foreground_32 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); + } + + SkBitmap* complete = (size == BIG) ? g_foreground_32 : g_foreground_16; + + // Mirror the positions if necessary. + gfx::Rect complete_bounds(origin_x, origin_y, + complete->width(), complete->height()); + complete_bounds.set_x( + containing_view->MirroredLeftPointForRect(complete_bounds)); + + // Start at full opacity, then loop back and forth five times before ending + // at zero opacity. + static const double PI = 3.141592653589793; + double opacity = sin(animation_progress * PI * kCompleteAnimationCycles + + PI/2) / 2 + 0.5; + + SkRect bounds; + bounds.set(SkIntToScalar(complete_bounds.x()), + SkIntToScalar(complete_bounds.y()), + SkIntToScalar(complete_bounds.x() + complete_bounds.width()), + SkIntToScalar(complete_bounds.y() + complete_bounds.height())); + canvas->saveLayerAlpha(&bounds, + static_cast<int>(255.0 * opacity), + SkCanvas::kARGB_ClipLayer_SaveFlag); + canvas->drawARGB(0, 255, 255, 255, SkPorterDuff::kClear_Mode); + canvas->DrawBitmapInt(*complete, complete_bounds.x(), complete_bounds.y()); + canvas->restore(); +} + +// Download dragging +void DragDownload(const DownloadItem* download, SkBitmap* icon) { + DCHECK(download); + + // Set up our OLE machinery + scoped_refptr<OSExchangeData> data(new OSExchangeData); + if (icon) + drag_utils::CreateDragImageForFile(download->file_name(), icon, data); + data->SetFilename(download->full_path()); + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + + // Run the drag and drop loop + DWORD effects; + DoDragDrop(data.get(), drag_source.get(), DROPEFFECT_COPY | DROPEFFECT_LINK, + &effects); +} + + +} // namespace download_util + diff --git a/chrome/browser/download/download_util.h b/chrome/browser/download/download_util.h new file mode 100644 index 0000000..0ad10ab --- /dev/null +++ b/chrome/browser/download/download_util.h @@ -0,0 +1,192 @@ +// 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. +// +// Download utilities. + +#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_UTIL_H__ +#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_UTIL_H__ + +#include <objidl.h> + +#include "base/basictypes.h" +#include "base/task.h" +#include "chrome/browser/views/download_item_view.h" +#include "chrome/views/event.h" +#include "chrome/views/menu.h" +#include "chrome/views/view.h" + +class DownloadItem; +class SkBitmap; + +namespace download_util { + +// DownloadContextMenu --------------------------------------------------------- + +// The base class of context menus that provides the various commands. +// Subclasses are responsible for creating and running the menu. +class BaseContextMenu : public Menu::Delegate { + public: + explicit BaseContextMenu(DownloadItem* download); + virtual ~BaseContextMenu(); + + enum ContextMenuCommands { + SHOW_IN_FOLDER = 1, // Open an Explorer window with the item highlighted + COPY_LINK, // Copy the download's URL to the clipboard + COPY_PATH, // Copy the download's full path to the clipboard + COPY_FILE, // Copy the downloaded file to the clipboard + OPEN_WHEN_COMPLETE, // Open the download when it's finished + ALWAYS_OPEN_TYPE, // Default this file extension to always open + REMOVE_ITEM, // Remove the download + CANCEL, // Cancel the download + MENU_LAST + }; + + // Menu::Delegate interface + virtual bool IsItemChecked(int id) const; + virtual bool IsItemDefault(int id) const; + virtual std::wstring GetLabel(int id) const; + virtual bool SupportsCommand(int id) const; + virtual bool IsCommandEnabled(int id) const; + virtual void ExecuteCommand(int id); + + protected: + // Information source. + DownloadItem* download_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(BaseContextMenu); +}; + +// Menu for the download shelf. +class DownloadShelfContextMenu : public BaseContextMenu { + public: + DownloadShelfContextMenu(DownloadItem* download, + HWND window, + DownloadItemView::BaseDownloadItemModel* model, + const CPoint& point); + virtual ~DownloadShelfContextMenu(); + + virtual bool IsItemDefault(int id) const; + virtual void ExecuteCommand(int id); + + private: + // A model to control the cancel behavior. + DownloadItemView::BaseDownloadItemModel* model_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadShelfContextMenu); +}; + +// Menu for the download destination view. +class DownloadDestinationContextMenu : public BaseContextMenu { + public: + DownloadDestinationContextMenu(DownloadItem* download, + HWND window, + const CPoint& point); + virtual ~DownloadDestinationContextMenu(); + + private: + DISALLOW_EVIL_CONSTRUCTORS(DownloadDestinationContextMenu); +}; + +// DownloadProgressTask -------------------------------------------------------- + +// A class for managing the timed progress animations for a download view. The +// view must implement an UpdateDownloadProgress() method. +template<class DownloadView> +class DownloadProgressTask : public Task { + public: + DownloadProgressTask(DownloadView* view) : view_(view) {} + virtual ~DownloadProgressTask() {} + virtual void Run() { + view_->UpdateDownloadProgress(); + } + private: + DownloadView* view_; + DISALLOW_EVIL_CONSTRUCTORS(DownloadProgressTask); +}; + +// Download opening ------------------------------------------------------------ + +// Whether it is OK to open this download. +bool CanOpenDownload(DownloadItem* download); + +// Open the file associated with this download (wait for the download to +// complete if it is in progress). +void OpenDownload(DownloadItem* download); + +// Download progress animations ------------------------------------------------ + +// Arc sweep angle for use with downloads of unknown size +const int kUnknownAngleDegrees = 50; + +// Rate of progress for use with downloads of unknown size +const int kUnknownIncrementDegrees = 12; + +// Start angle for downloads with known size (midnight position) +const int kStartAngleDegrees = -90; + +// A circle +const int kMaxDegrees = 360; + +// Progress animation timer period, in milliseconds. +const int kProgressRateMs = 150; + +// XP and Vista must support icons of this size. +const int kSmallIconSize = 16; +const int kBigIconSize = 32; + +// Our progress halo around the icon +const int kSmallProgressIconSize = 39; +const int kBigProgressIconSize = 52; + +// The offset required to center the icon in the progress bitmaps. +const int kSmallProgressIconOffset = + (kSmallProgressIconSize - kSmallIconSize) / 2; +const int kBigProgressIconOffset = (kBigProgressIconSize - kBigIconSize) / 2; + +enum PaintDownloadProgressSize { + SMALL = 0, + BIG +}; + +// Paint the common download animation progress foreground and background, +// clipping the foreground to 'percent' full. If percent is -1, then we don't +// know the total size, so we just draw a rotating segment until we're done. +// +// |containing_view| is the View subclass within which the progress animation +// is drawn (generally either DownloadItemTabView or DownloadItemView). We +// require the containing View in addition to the canvas because if we are +// drawing in a right-to-left locale, we need to mirror the position of the +// progress animation within the containing View. +void PaintDownloadProgress(ChromeCanvas* canvas, + ChromeViews::View* containing_view, + int origin_x, + int origin_y, + int start_angle, + int percent, + PaintDownloadProgressSize size); + +void PaintDownloadComplete(ChromeCanvas* canvas, + ChromeViews::View* containing_view, + int origin_x, + int origin_y, + double animation_progress, + PaintDownloadProgressSize size); + +// Drag support ---------------------------------------------------------------- + +// Helper function for download views to use when acting as a drag source for a +// DownloadItem. If 'icon' is NULL, no image will be accompany the drag. +void DragDownload(const DownloadItem* download, SkBitmap* icon); + +// Executable file support ----------------------------------------------------- + +// Copy all executable file extensions. +void InitializeExeTypes(std::set<std::wstring>* exe_extensions); + +} // namespace download_util + + +#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_UTIL_H__ + |