summaryrefslogtreecommitdiffstats
path: root/chrome/browser/download
diff options
context:
space:
mode:
authorpaulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-13 02:48:59 +0000
committerpaulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-13 02:48:59 +0000
commitcdaa86594dcdd5fc4ccac5dbf63a5e4dbfcdb8ea (patch)
treec9e3f5a89a3d9733ea75b97153ef2be4e8425aa3 /chrome/browser/download
parent431838df78d6e3cd06794a45cf0243be32149c7c (diff)
downloadchromium_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.cc137
-rw-r--r--chrome/browser/download/download_file.cc580
-rw-r--r--chrome/browser/download/download_file.h273
-rw-r--r--chrome/browser/download/download_item_model.cc89
-rw-r--r--chrome/browser/download/download_item_model.h34
-rw-r--r--chrome/browser/download/download_manager.cc1104
-rw-r--r--chrome/browser/download/download_manager.h483
-rw-r--r--chrome/browser/download/download_manager_unittest.cc320
-rw-r--r--chrome/browser/download/download_uitest.cc241
-rw-r--r--chrome/browser/download/download_util.cc407
-rw-r--r--chrome/browser/download/download_util.h192
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__
+