summaryrefslogtreecommitdiffstats
path: root/chrome/browser/download
diff options
context:
space:
mode:
authorpaulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-14 01:09:50 +0000
committerpaulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-14 01:09:50 +0000
commit37936eefdf1bf98d1a4458d8f3b85233d28c37e4 (patch)
tree96e1ffcf546b7fb4ad458f9a2ab8b8268d5f122a /chrome/browser/download
parentda5de3c98d0613411df3acb9b692fa200e51dcfb (diff)
downloadchromium_src-37936eefdf1bf98d1a4458d8f3b85233d28c37e4.zip
chromium_src-37936eefdf1bf98d1a4458d8f3b85233d28c37e4.tar.gz
chromium_src-37936eefdf1bf98d1a4458d8f3b85233d28c37e4.tar.bz2
Move the Save Page code to the browser/download/ directory.
Review URL: http://codereview.chromium.org/3040 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2174 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/download')
-rw-r--r--chrome/browser/download/save_file.cc103
-rw-r--r--chrome/browser/download/save_file.h80
-rw-r--r--chrome/browser/download/save_file_manager.cc582
-rw-r--r--chrome/browser/download/save_file_manager.h262
-rw-r--r--chrome/browser/download/save_item.cc132
-rw-r--r--chrome/browser/download/save_item.h112
-rw-r--r--chrome/browser/download/save_package.cc1064
-rw-r--r--chrome/browser/download/save_package.h309
-rw-r--r--chrome/browser/download/save_package_unittest.cc170
-rw-r--r--chrome/browser/download/save_page_model.cc48
-rw-r--r--chrome/browser/download/save_page_model.h38
-rw-r--r--chrome/browser/download/save_page_uitest.cc112
-rw-r--r--chrome/browser/download/save_types.h71
13 files changed, 3083 insertions, 0 deletions
diff --git a/chrome/browser/download/save_file.cc b/chrome/browser/download/save_file.cc
new file mode 100644
index 0000000..b92edf2
--- /dev/null
+++ b/chrome/browser/download/save_file.cc
@@ -0,0 +1,103 @@
+// 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/save_file.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "chrome/browser/download/save_types.h"
+#include "chrome/common/win_util.h"
+#include "chrome/common/win_safe_util.h"
+
+SaveFile::SaveFile(const SaveFileCreateInfo* info)
+ : info_(info),
+ file_(NULL),
+ bytes_so_far_(0),
+ path_renamed_(false),
+ in_progress_(true) {
+ DCHECK(info);
+ DCHECK(info->path.empty());
+ if (file_util::CreateTemporaryFileName(&full_path_))
+ Open(L"wb");
+}
+
+SaveFile::~SaveFile() {
+ Close();
+}
+
+// Return false indicate that we got disk error, save file manager will tell
+// SavePackage this error, then SavePackage will call its Cancel() method to
+// cancel whole save job.
+bool SaveFile::AppendDataToFile(const char* data, int data_len) {
+ if (file_) {
+ if (data_len == fwrite(data, 1, data_len, file_)) {
+ bytes_so_far_ += data_len;
+ return true;
+ } else {
+ Close();
+ return false;
+ }
+ }
+ // No file_, treat it as disk error.
+ return false;
+}
+
+void SaveFile::Cancel() {
+ Close();
+ // If this job has been canceled, and it has created file,
+ // We need to delete this created file.
+ if (!full_path_.empty()) {
+ DeleteFile(full_path_.c_str());
+ }
+}
+
+// Rename the file when we have final name.
+bool SaveFile::Rename(const std::wstring& new_path) {
+ Close();
+
+ DCHECK(!path_renamed());
+ // 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 the last parameter
+ // is FALSE and the new file already exists, the function overwrites the
+ // existing file and succeeds.
+ if (!CopyFile(full_path_.c_str(), new_path.c_str(), FALSE))
+ return false;
+
+ DeleteFile(full_path_.c_str());
+
+ full_path_ = new_path;
+ path_renamed_ = true;
+
+ // Still in saving process, reopen the file.
+ if (in_progress_ && !Open(L"a+b"))
+ return false;
+ return true;
+}
+
+void SaveFile::Finish() {
+ Close();
+ in_progress_ = false;
+}
+
+void SaveFile::Close() {
+ if (file_) {
+ fclose(file_);
+ file_ = NULL;
+ }
+}
+
+bool SaveFile::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;
+}
+
diff --git a/chrome/browser/download/save_file.h b/chrome/browser/download/save_file.h
new file mode 100644
index 0000000..3d5e06c
--- /dev/null
+++ b/chrome/browser/download/save_file.h
@@ -0,0 +1,80 @@
+// 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_SAVE_FILE_H__
+#define CHROME_BROWSER_DOWNLOAD_SAVE_FILE_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/download/save_types.h"
+
+// SaveFile ----------------------------------------------------------------
+
+// These objects live exclusively on the file thread and handle the writing
+// operations for one save item. These objects live only for the duration that
+// the saving job is 'in progress': once the saving job has been completed or
+// canceled, the SaveFile is destroyed. One SaveFile object represents one item
+// in a save session.
+class SaveFile {
+ public:
+ explicit SaveFile(const SaveFileCreateInfo* info);
+ ~SaveFile();
+
+ // Write a new chunk of data to the file. Returns true on success.
+ bool AppendDataToFile(const char* data, int data_len);
+
+ // Abort the saving job and automatically close the file.
+ void Cancel();
+
+ // Rename the saved file. Returns 'true' if the rename was successful.
+ bool Rename(const std::wstring& full_path);
+
+ void Finish();
+
+ // Accessors.
+ int save_id() const { return info_->save_id; }
+ int render_process_id() const { return info_->render_process_id; }
+ int render_view_id() const { return info_->render_view_id; }
+ int request_id() const { return info_->request_id; }
+ SaveFileCreateInfo::SaveFileSource save_source() const {
+ return info_->save_source;
+ }
+
+ int64 bytes_so_far() const { return bytes_so_far_; }
+ std::wstring full_path() const { return full_path_; }
+ bool path_renamed() const { return path_renamed_; }
+ bool in_progress() const { return 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);
+
+ scoped_ptr<const SaveFileCreateInfo> info_;
+
+ // OS file handle for writing
+ FILE* file_;
+
+ // 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 saved file including the file name.
+ std::wstring full_path_;
+
+ // Whether the saved file is still using its initial temporary path.
+ bool path_renamed_;
+
+ // Whether the saved file is still receiving data.
+ bool in_progress_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SaveFile);
+};
+
+#endif // CHROME_BROWSER_DOWNLOAD_SAVE_FILE_H__
+
diff --git a/chrome/browser/download/save_file_manager.cc b/chrome/browser/download/save_file_manager.cc
new file mode 100644
index 0000000..2a909b72
--- /dev/null
+++ b/chrome/browser/download/save_file_manager.cc
@@ -0,0 +1,582 @@
+// 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/save_file_manager.h"
+
+#include "base/file_util.h"
+#include "base/logging.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/save_file.h"
+#include "chrome/browser/download/save_package.h"
+#include "chrome/browser/resource_dispatcher_host.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/tab_util.h"
+#include "chrome/browser/web_contents.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"
+
+SaveFileManager::SaveFileManager(MessageLoop* ui_loop,
+ MessageLoop* io_loop,
+ ResourceDispatcherHost* rdh)
+ : next_id_(0),
+ ui_loop_(ui_loop),
+ io_loop_(io_loop),
+ resource_dispatcher_host_(rdh) {
+ DCHECK(ui_loop_);
+ // Need to make sure that we are in UI thread because using g_browser_process
+ // on a non-UI thread can cause crashes during shutdown.
+ DCHECK(ui_loop_ == MessageLoop::current());
+ // Cache the message loop of file thread.
+ base::Thread* thread = g_browser_process->file_thread();
+ if (thread)
+ file_loop_ = thread->message_loop();
+ else
+ // It could be NULL when it is created in unit test of
+ // ResourceDispatcherHost.
+ file_loop_ = NULL;
+ DCHECK(resource_dispatcher_host_);
+}
+
+SaveFileManager::~SaveFileManager() {
+ // Check for clean shutdown.
+ DCHECK(save_file_map_.empty());
+}
+
+// Called during the browser shutdown process to clean up any state (open files,
+// timers) that live on the saving thread (file thread).
+void SaveFileManager::Shutdown() {
+ MessageLoop* loop = GetSaveLoop();
+ if (loop) {
+ loop->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &SaveFileManager::OnShutdown));
+ }
+}
+
+// Stop file thread operations.
+void SaveFileManager::OnShutdown() {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ STLDeleteValues(&save_file_map_);
+}
+
+SaveFile* SaveFileManager::LookupSaveFile(int save_id) {
+ SaveFileMap::iterator it = save_file_map_.find(save_id);
+ return it == save_file_map_.end() ? NULL : it->second;
+}
+
+// Called on the IO thread when
+// a) The ResourceDispatcherHost has decided that a request is savable.
+// b) The resource does not come from the network, but we still need a
+// save ID for for managing the status of the saving operation. So we
+// file a request from the file thread to the IO thread to generate a
+// unique save ID.
+int SaveFileManager::GetNextId() {
+ DCHECK(MessageLoop::current() == io_loop_);
+ return next_id_++;
+}
+
+void SaveFileManager::RegisterStartingRequest(const std::wstring& save_url,
+ SavePackage* save_package) {
+ // Make sure it runs in the UI thread.
+ DCHECK(MessageLoop::current() == ui_loop_);
+ int tab_id = save_package->GetTabId();
+
+ // Register this starting request.
+ StartingRequestsMap& starting_requests = tab_starting_requests_[tab_id];
+ bool never_present = starting_requests.insert(
+ StartingRequestsMap::value_type(save_url, save_package)).second;
+ DCHECK(never_present);
+}
+
+SavePackage* SaveFileManager::UnregisterStartingRequest(
+ const std::wstring& save_url, int tab_id) {
+ // Make sure it runs in UI thread.
+ DCHECK(MessageLoop::current() == ui_loop_);
+
+ TabToStartingRequestsMap::iterator it = tab_starting_requests_.find(tab_id);
+ if (it != tab_starting_requests_.end()) {
+ StartingRequestsMap& requests = it->second;
+ StartingRequestsMap::iterator sit = requests.find(save_url);
+ if (sit == requests.end())
+ return NULL;
+
+ // Found, erase it from starting list and return SavePackage.
+ SavePackage* save_package = sit->second;
+ requests.erase(sit);
+ // If there is no element in requests, remove it
+ if (requests.empty())
+ tab_starting_requests_.erase(it);
+ return save_package;
+ }
+
+ return NULL;
+}
+
+void SaveFileManager::RequireSaveJobFromOtherSource(SaveFileCreateInfo* info) {
+ // This function must be called on the UI thread, because the io_loop_
+ // pointer may be junk when we use it on file thread. We can only rely on the
+ // io_loop_ pointer being valid when we run code on the UI thread (or on
+ // the IO thread.
+ DCHECK(MessageLoop::current() == ui_loop_);
+ DCHECK(info->save_id == -1);
+ // Since the data will come from render process, so we need to start
+ // this kind of save job by ourself.
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnRequireSaveJobFromOtherSource,
+ info));
+}
+
+// Look up a SavePackage according to a save id.
+SavePackage* SaveFileManager::LookupPackage(int save_id) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ SavePackageMap::iterator it = packages_.find(save_id);
+ if (it != packages_.end())
+ return it->second;
+ return NULL;
+}
+
+// Call from SavePackage for starting a saving job
+void SaveFileManager::SaveURL(const std::wstring& url,
+ const std::wstring& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ SaveFileCreateInfo::SaveFileSource save_source,
+ const std::wstring& file_full_path,
+ URLRequestContext* request_context,
+ SavePackage* save_package) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ if (!io_loop_) {
+ NOTREACHED(); // Net IO thread must exist.
+ return;
+ }
+
+ // Register a saving job.
+ RegisterStartingRequest(url, save_package);
+ if (save_source == SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
+ GURL save_url(url);
+ DCHECK(save_url.is_valid());
+
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnSaveURL,
+ save_url,
+ GURL(referrer),
+ render_process_host_id,
+ render_view_id,
+ request_context));
+ } else {
+ // We manually start the save job.
+ SaveFileCreateInfo* info = new SaveFileCreateInfo(file_full_path,
+ url,
+ save_source,
+ -1);
+ info->render_process_id = render_process_host_id;
+ info->render_view_id = render_view_id;
+ RequireSaveJobFromOtherSource(info);
+ }
+}
+
+// Utility function for look up table maintenance, called on the UI thread.
+// A manager may have multiple save page job (SavePackage) in progress,
+// so we just look up the save id and remove it from the tracking table.
+// If the save id is -1, it means we just send a request to save, but the
+// saving action has still not happened, need to call UnregisterStartingRequest
+// to remove it from the tracking map.
+void SaveFileManager::RemoveSaveFile(int save_id, const std::wstring& save_url,
+ SavePackage* package) {
+ DCHECK(MessageLoop::current() == ui_loop_ && package);
+ // A save page job(SavePackage) can only have one manager,
+ // so remove it if it exists.
+ if (save_id == -1) {
+ SavePackage* old_package = UnregisterStartingRequest(save_url,
+ package->GetTabId());
+ DCHECK(old_package == package);
+ } else {
+ SavePackageMap::iterator it = packages_.find(save_id);
+ if (it != packages_.end())
+ packages_.erase(it);
+ }
+}
+
+// Static
+// Utility function for converting request IDs to a TabContents. Must be called
+// only on the UI thread.
+SavePackage* SaveFileManager::GetSavePackageFromRenderIds(
+ 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) {
+ // Convert const pointer of WebContents to pointer of WebContents.
+ const WebContents* web_contents = contents->AsWebContents();
+ if (web_contents)
+ return web_contents->get_save_package();
+ }
+
+ return NULL;
+}
+
+// Utility function for deleting specified file.
+void SaveFileManager::DeleteDirectoryOrFile(const std::wstring& full_path,
+ bool is_dir) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ MessageLoop* loop = GetSaveLoop();
+ DCHECK(loop);
+ loop->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnDeleteDirectoryOrFile,
+ full_path,
+ is_dir));
+}
+
+void SaveFileManager::SendCancelRequest(int save_id) {
+ // Cancel the request which has specific save id.
+ DCHECK(save_id > -1);
+ MessageLoop* loop = GetSaveLoop();
+ DCHECK(loop);
+ loop->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::CancelSave,
+ save_id));
+}
+
+// Notifications sent from the IO thread and run on the file thread:
+
+// The IO thread created |info|, but the file thread (this method) uses it
+// to create a SaveFile which will hold and finally destroy |info|. It will
+// then passes |info| to the UI thread for reporting saving status.
+void SaveFileManager::StartSave(SaveFileCreateInfo* info) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ DCHECK(info);
+ SaveFile* save_file = new SaveFile(info);
+ DCHECK(LookupSaveFile(info->save_id) == NULL);
+ save_file_map_[info->save_id] = save_file;
+ info->path = save_file->full_path();
+
+ ui_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnStartSave,
+ info));
+}
+
+// We do forward an update to the UI thread here, since we do not use timer to
+// update the UI. If the user has canceled the saving action (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 SaveFile has been deleted.
+void SaveFileManager::UpdateSaveProgress(int save_id,
+ char* data,
+ int data_len) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ SaveFile* save_file = LookupSaveFile(save_id);
+ if (save_file) {
+ bool write_success = save_file->AppendDataToFile(data, data_len);
+ ui_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnUpdateSaveProgress,
+ save_file->save_id(),
+ save_file->bytes_so_far(),
+ write_success));
+ }
+ delete [] data;
+}
+
+// The IO thread will call this when saving is completed or it got error when
+// fetching data. In the former case, we forward the message to OnSaveFinished
+// in UI thread. In the latter case, the save ID will be -1, which means the
+// saving action did not even start, so we need to call OnErrorFinished in UI
+// thread, which will use the save URL to find corresponding request record and
+// delete it.
+void SaveFileManager::SaveFinished(int save_id,
+ std::wstring save_url,
+ int render_process_id,
+ bool is_success) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ SaveFileMap::iterator it = save_file_map_.find(save_id);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+ ui_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnSaveFinished,
+ save_id,
+ save_file->bytes_so_far(),
+ is_success));
+
+ save_file->Finish();
+ } else if (save_id == -1) {
+ // Before saving started, we got error. We still call finish process.
+ DCHECK(!save_url.empty());
+ ui_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnErrorFinished,
+ save_url,
+ render_process_id));
+ }
+}
+
+// Notifications sent from the file thread and run on the UI thread.
+
+void SaveFileManager::OnStartSave(const SaveFileCreateInfo* info) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ SavePackage* save_package =
+ GetSavePackageFromRenderIds(info->render_process_id,
+ info->render_view_id);
+ if (!save_package) {
+ // Cancel this request.
+ SendCancelRequest(info->save_id);
+ return;
+ }
+
+ // Insert started saving job to tracking list.
+ SavePackageMap::iterator sit = packages_.find(info->save_id);
+ if (sit == packages_.end()) {
+ // Find the registered request. If we can not find, it means we have
+ // canceled the job before.
+ SavePackage* old_save_package = UnregisterStartingRequest(info->url,
+ info->render_process_id);
+ if (!old_save_package) {
+ // Cancel this request.
+ SendCancelRequest(info->save_id);
+ return;
+ }
+ DCHECK(old_save_package == save_package);
+ packages_[info->save_id] = save_package;
+ } else {
+ NOTREACHED();
+ }
+
+ // Forward this message to SavePackage.
+ save_package->StartSave(info);
+}
+
+void SaveFileManager::OnUpdateSaveProgress(int save_id, int64 bytes_so_far,
+ bool write_success) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ SavePackage* package = LookupPackage(save_id);
+ if (package)
+ package->UpdateSaveProgress(save_id, bytes_so_far, write_success);
+ else
+ SendCancelRequest(save_id);
+}
+
+void SaveFileManager::OnSaveFinished(int save_id,
+ int64 bytes_so_far,
+ bool is_success) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ SavePackage* package = LookupPackage(save_id);
+ if (package)
+ package->SaveFinished(save_id, bytes_so_far, is_success);
+}
+
+void SaveFileManager::OnErrorFinished(std::wstring save_url, int tab_id) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ SavePackage* save_package = UnregisterStartingRequest(save_url, tab_id);
+ if (save_package)
+ save_package->SaveFailed(save_url);
+}
+
+void SaveFileManager::OnCancelSaveRequest(int render_process_id,
+ int request_id) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+ DCHECK(io_loop_);
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::ExecuteCancelSaveRequest,
+ render_process_id,
+ request_id));
+}
+
+// Notifications sent from the UI thread and run on the IO thread.
+
+void SaveFileManager::OnSaveURL(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_->BeginSaveFile(url,
+ referrer,
+ render_process_host_id,
+ render_view_id,
+ request_context);
+}
+
+void SaveFileManager::OnRequireSaveJobFromOtherSource(
+ SaveFileCreateInfo* info) {
+ DCHECK(MessageLoop::current() == io_loop_);
+ DCHECK(info->save_id == -1);
+ // Generate a unique save id.
+ info->save_id = GetNextId();
+ // Start real saving action.
+ MessageLoop* loop = GetSaveLoop();
+ DCHECK(loop);
+ loop->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::StartSave,
+ info));
+}
+
+void SaveFileManager::ExecuteCancelSaveRequest(int render_process_id,
+ int request_id) {
+ DCHECK(MessageLoop::current() == io_loop_);
+ resource_dispatcher_host_->CancelRequest(render_process_id,
+ request_id,
+ false);
+}
+
+// Notifications sent from the UI thread and run on the file thread.
+
+// This method will be sent via a user action, or shutdown on the UI thread,
+// and run on the file thread. We don't post a message back for cancels,
+// but we do forward the cancel to the IO thread. Since this message has been
+// sent from the UI thread, the saving job may have already completed and
+// won't exist in our map.
+void SaveFileManager::CancelSave(int save_id) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ SaveFileMap::iterator it = save_file_map_.find(save_id);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+
+ // If the data comes from the net IO thread, then forward the cancel
+ // message to IO thread. If the data comes from other sources, just
+ // ignore the cancel message.
+ if (save_file->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_NET) {
+ ui_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnCancelSaveRequest,
+ save_file->render_process_id(),
+ save_file->request_id()));
+
+ // UI thread will notify the render process to stop sending data,
+ // so in here, we need not to do anything, just close the save file.
+ save_file->Cancel();
+ } else {
+ // If we did not find SaveFile in map, the saving job should either get
+ // data from other sources or have finished.
+ DCHECK(save_file->save_source() !=
+ SaveFileCreateInfo::SAVE_FILE_FROM_NET ||
+ !save_file->in_progress());
+ }
+ // Whatever the save file is renamed or not, just delete it.
+ save_file_map_.erase(it);
+ delete save_file;
+ }
+}
+
+// It is possible that SaveItem which has specified save_id has been canceled
+// before this function runs. So if we can not find corresponding SaveFile by
+// using specified save_id, just return.
+void SaveFileManager::SaveLocalFile(const std::wstring& original_file_url,
+ int save_id,
+ int render_process_id) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ SaveFile* save_file = LookupSaveFile(save_id);
+ if (!save_file)
+ return;
+ DCHECK(!save_file->path_renamed());
+ // If it has finished, just return.
+ if (!save_file->in_progress())
+ return;
+
+ // Close the save file before the copy operation.
+ save_file->Finish();
+
+ GURL file_url(original_file_url);
+ DCHECK(file_url.SchemeIsFile());
+ std::wstring file_path;
+ net::FileURLToFilePath(file_url, &file_path);
+ // If we can not get valid file path from original URL, treat it as
+ // disk error.
+ if (file_path.empty())
+ SaveFinished(save_id, original_file_url, render_process_id, false);
+
+ // Copy the local file to the temporary file. It will be renamed to its
+ // final name later.
+ bool success = CopyFile(file_path.c_str(),
+ save_file->full_path().c_str(), FALSE) != 0;
+ if (!success)
+ file_util::Delete(save_file->full_path(), false);
+ SaveFinished(save_id, original_file_url, render_process_id, success);
+}
+
+void SaveFileManager::OnDeleteDirectoryOrFile(const std::wstring& full_path,
+ bool is_dir) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ DCHECK(!full_path.empty());
+
+ file_util::Delete(full_path, is_dir);
+}
+
+// Open a saved page package, show it in a Windows Explorer window.
+// We run on this thread to avoid blocking the UI with slow Shell operations.
+void SaveFileManager::OnShowSavedFileInShell(const std::wstring full_path) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+ win_util::ShowItemInFolder(full_path);
+}
+
+void SaveFileManager::RenameAllFiles(
+ const FinalNameList& final_names,
+ const std::wstring& resource_dir,
+ int render_process_id,
+ int render_view_id) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+
+ if (!resource_dir.empty() && !file_util::PathExists(resource_dir))
+ file_util::CreateDirectory(resource_dir);
+
+ for (FinalNameList::const_iterator i = final_names.begin();
+ i != final_names.end(); ++i) {
+ SaveFileMap::iterator it = save_file_map_.find(i->first);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+ DCHECK(!save_file->in_progress());
+ save_file->Rename(i->second);
+ delete save_file;
+ save_file_map_.erase(it);
+ }
+ }
+
+ ui_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SaveFileManager::OnFinishSavePageJob,
+ render_process_id,
+ render_view_id));
+}
+
+void SaveFileManager::OnFinishSavePageJob(int render_process_id,
+ int render_view_id) {
+ DCHECK(MessageLoop::current() == ui_loop_);
+
+ SavePackage* save_package =
+ GetSavePackageFromRenderIds(render_process_id, render_view_id);
+
+ save_package->Finish();
+}
+
+void SaveFileManager::RemoveSavedFileFromFileMap(
+ const SaveIDList& save_ids) {
+ DCHECK(MessageLoop::current() == GetSaveLoop());
+
+ for (SaveIDList::const_iterator i = save_ids.begin();
+ i != save_ids.end(); ++i) {
+ SaveFileMap::iterator it = save_file_map_.find(*i);
+ if (it != save_file_map_.end()) {
+ SaveFile* save_file = it->second;
+ DCHECK(!save_file->in_progress());
+ DeleteFile(save_file->full_path().c_str());
+ delete save_file;
+ save_file_map_.erase(it);
+ }
+ }
+}
+
diff --git a/chrome/browser/download/save_file_manager.h b/chrome/browser/download/save_file_manager.h
new file mode 100644
index 0000000..8bd6e89
--- /dev/null
+++ b/chrome/browser/download/save_file_manager.h
@@ -0,0 +1,262 @@
+// 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 saving files, on the file thread.
+//
+// The SaveFileManager owns a set of SaveFile objects, each of which connects
+// with a SaveItem object which belongs to one SavePackage and runs on the file
+// thread for saving data in order to avoid disk activity on either network IO
+// thread or the UI thread. It coordinates the notifications from the network
+// and UI.
+//
+// The SaveFileManager itself is a singleton object owned by the
+// ResourceDispatcherHost.
+//
+// The data sent to SaveFileManager have 2 sources, one is from
+// ResourceDispatcherHost, run in network IO thread, the all sub-resources
+// and save-only-HTML pages will be got from network IO. The second is from
+// render process, those html pages which are serialized from DOM will be
+// composed in render process and encoded to its original encoding, then sent
+// to UI loop in browser process, then UI loop will dispatch the data to
+// SaveFileManager on the file thread. SaveFileManager will directly
+// call SaveFile's method to persist data.
+//
+// A typical saving job operation involves multiple threads:
+//
+// Updating an in progress save file
+// io_thread
+// |----> data from net ---->|
+// |
+// |
+// |----> data from ---->| |
+// | render process | |
+// ui_thread | |
+// file_thread (writes to disk)
+// |----> stats ---->|
+// ui_thread (feedback for user)
+//
+//
+// 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
+// ui_thread (user close tab) for saving)
+// |----> cancel command ---->|
+// Render process(stop serializing DOM and sending
+// data)
+//
+//
+// The SaveFileManager tracks saving requests, mapping from a save ID
+// (unique integer created in the IO thread) to the SavePackage for the
+// tab where the saving job was initiated. In the event of a tab closure
+// during saving, the SavePackage will notice the SaveFileManage to
+// cancel all SaveFile job.
+
+#ifndef CHROME_BROWSER_DOWNLOAD_SAVE_FILE_MANAGER_H__
+#define CHROME_BROWSER_DOWNLOAD_SAVE_FILE_MANAGER_H__
+
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/hash_tables.h"
+#include "base/ref_counted.h"
+#include "base/thread.h"
+#include "chrome/browser/download/save_types.h"
+
+class GURL;
+class SaveFile;
+class SavePackage;
+class MessageLoop;
+class ResourceDispatcherHost;
+class Task;
+class URLRequestContext;
+
+class SaveFileManager
+ : public base::RefCountedThreadSafe<SaveFileManager> {
+ public:
+ SaveFileManager(MessageLoop* ui_loop,
+ MessageLoop* io_loop,
+ ResourceDispatcherHost* rdh);
+ ~SaveFileManager();
+
+ // Lifetime management.
+ void Shutdown();
+
+ // Called on the IO thread
+ int GetNextId();
+
+ // Save the specified URL. Called on the UI thread and forwarded to the
+ // ResourceDispatcherHost on the IO thread.
+ void SaveURL(const std::wstring& url,
+ const std::wstring& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ SaveFileCreateInfo::SaveFileSource save_source,
+ const std::wstring& file_full_path,
+ URLRequestContext* request_context,
+ SavePackage* save_package);
+
+ // Notifications sent from the IO thread and run on the file thread:
+ void StartSave(SaveFileCreateInfo* info);
+ void UpdateSaveProgress(int save_id, char* data, int size);
+ void SaveFinished(int save_id, std::wstring save_url,
+ int render_process_id, bool is_success);
+
+ // Notifications sent from the UI thread and run on the file thread.
+ // Cancel a SaveFile instance which has specified save id.
+ void CancelSave(int save_id);
+
+ // Called on the UI thread to remove a save package from SaveFileManager's
+ // tracking map.
+ void RemoveSaveFile(int save_id, const std::wstring& save_url,
+ SavePackage* package);
+
+ // Handler for shell operations sent from the UI to the file thread.
+ void OnShowSavedFileInShell(const std::wstring full_path);
+
+ // Helper function for deleting specified file.
+ void DeleteDirectoryOrFile(const std::wstring& full_path, bool is_dir);
+
+ // For posting notifications from the UI and IO threads.
+ MessageLoop* GetSaveLoop() const { return file_loop_; }
+
+ // Runs on file thread to save a file by copying from file system when
+ // original url is using file scheme.
+ void SaveLocalFile(const std::wstring& original_file_url,
+ int save_id,
+ int render_process_id);
+
+ // Renames all the successfully saved files.
+ // |final_names| points to a vector which contains pairs of save ids and
+ // final names of successfully saved files.
+ void RenameAllFiles(
+ const FinalNameList& final_names,
+ const std::wstring& resource_dir,
+ int render_process_id,
+ int render_view_id);
+
+ // When the user cancels the saving, we need to remove all remaining saved
+ // files of this page saving job from save_file_map_.
+ void RemoveSavedFileFromFileMap(const SaveIDList & save_ids);
+
+ private:
+ // A cleanup helper that runs on the file thread.
+ void OnShutdown();
+
+ // The resource does not come from the network, but we still needs to call
+ // this function for getting unique save ID by calling
+ // OnRequireSaveJobFromOtherSource in the net IO thread and start saving
+ // operation. This function is called on the UI thread.
+ void RequireSaveJobFromOtherSource(SaveFileCreateInfo* info);
+
+ // Called only on UI thread to get the SavePackage for a tab's profile.
+ static SavePackage* GetSavePackageFromRenderIds(int render_process_id,
+ int review_view_id);
+
+ // Register a starting request. Associate the save URL with a
+ // SavePackage for further matching.
+ void RegisterStartingRequest(const std::wstring& save_url,
+ SavePackage* save_package);
+ // Unregister a start request according save URL, disassociate
+ // the save URL and SavePackage.
+ SavePackage* UnregisterStartingRequest(const std::wstring& save_url,
+ int tab_id);
+
+ // Look up the SavePackage according to save id.
+ SavePackage* LookupPackage(int save_id);
+
+ // Called only on the file thread.
+ // Look up one in-progress saving item according to save id.
+ SaveFile* LookupSaveFile(int save_id);
+
+ // Help function for sending notification of canceling specific request.
+ void SendCancelRequest(int save_id);
+
+ // Notifications sent from the file thread and run on the UI thread.
+
+ // Lookup the SaveManager for this WebContents' saving profile and inform it
+ // the saving job has been started.
+ void OnStartSave(const SaveFileCreateInfo* info);
+ // Update the SavePackage with the current state of a started saving job.
+ // If the SavePackage for this saving job is gone, cancel the request.
+ void OnUpdateSaveProgress(int save_id,
+ int64 bytes_so_far,
+ bool write_success);
+ // Update the SavePackage with the finish state, and remove the request
+ // tracking entries.
+ void OnSaveFinished(int save_id, int64 bytes_so_far, bool is_success);
+ // For those requests that do not have valid save id, use
+ // map:(url, SavePackage) to find the request and remove it.
+ void OnErrorFinished(std::wstring save_url, int tab_id);
+ // Handler for a notification sent to the UI thread.
+ // The user has requested a cancel in the UI thread, so send a cancel request
+ // to stop the network requests in net IO thread.
+ void OnCancelSaveRequest(int render_process_id, int request_id);
+ // Notifies SavePackage that the whole page saving job is finished.
+ void OnFinishSavePageJob(int render_process_id, int render_view_id);
+
+ // Notifications sent from the UI thread and run on the file thread.
+
+ // Deletes a specified file on the file thread.
+ void OnDeleteDirectoryOrFile(const std::wstring& full_path, bool is_dir);
+
+ // Notifications sent from the UI thread and run on the IO thread
+
+ // Initiates a request for URL to be saved.
+ void OnSaveURL(const GURL& url,
+ const GURL& referrer,
+ int render_process_host_id,
+ int render_view_id,
+ URLRequestContext* request_context);
+ // Handler for a notification sent to the IO thread for generating save id.
+ void OnRequireSaveJobFromOtherSource(SaveFileCreateInfo* info);
+ // Call ResourceDispatcherHost's CancelRequest method to execute cancel
+ // action in the IO thread.
+ void ExecuteCancelSaveRequest(int render_process_id, int request_id);
+
+ // Unique ID for the next SaveFile object.
+ int next_id_;
+
+ // A map of all saving jobs by using save id.
+ typedef base::hash_map<int, SaveFile*> SaveFileMap;
+ SaveFileMap save_file_map_;
+
+ // Message loop that the SavePackages live on.
+ MessageLoop* ui_loop_;
+
+ // We cache the IO loop, we will use it to request resources from network.
+ MessageLoop* io_loop_;
+
+ // We cache the file loop, we will use it to do real file operation.
+ // We guarantee that we won't access them incorrectly during the shutdown
+ // process
+ MessageLoop* file_loop_;
+
+ ResourceDispatcherHost* resource_dispatcher_host_;
+
+ // Tracks which SavePackage to send data to, called only on UI thread.
+ // SavePackageMap maps save IDs to their SavePackage.
+ typedef base::hash_map<int, SavePackage*> SavePackageMap;
+ SavePackageMap packages_;
+
+ // There is a gap between after calling SaveURL() and before calling
+ // StartSave(). In this gap, each request does not have save id for tracking.
+ // But sometimes users might want to stop saving job or ResourceDispatcherHost
+ // calls SaveFinished with save id -1 for network error. We name the requests
+ // as starting requests. For tracking those starting requests, we need to
+ // have some data structure.
+ // First we use a hashmap to map the request URL to SavePackage, then we
+ // use a hashmap to map the tab id (we actually use render_process_id) to the
+ // hashmap since it is possible to save same URL in different tab at
+ // same time.
+ typedef base::hash_map<std::wstring, SavePackage*> StartingRequestsMap;
+ typedef base::hash_map<int, StartingRequestsMap> TabToStartingRequestsMap;
+ TabToStartingRequestsMap tab_starting_requests_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SaveFileManager);
+};
+
+#endif // CHROME_BROWSER_DOWNLOAD_SAVE_FILE_MANAGER_H__
diff --git a/chrome/browser/download/save_item.cc b/chrome/browser/download/save_item.cc
new file mode 100644
index 0000000..b7b7ee40
--- /dev/null
+++ b/chrome/browser/download/save_item.cc
@@ -0,0 +1,132 @@
+// 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/save_item.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "chrome/browser/download/save_file.h"
+#include "chrome/browser/download/save_file_manager.h"
+#include "chrome/browser/download/save_package.h"
+
+// Constructor for SaveItem when creating each saving job.
+SaveItem::SaveItem(const std::wstring& url,
+ const std::wstring& referrer,
+ SavePackage* package,
+ SaveFileCreateInfo::SaveFileSource save_source)
+ : save_id_(-1),
+ save_source_(save_source),
+ url_(url),
+ referrer_(referrer),
+ total_bytes_(0),
+ received_bytes_(0),
+ state_(WAIT_START),
+ package_(package),
+ has_final_name_(false),
+ is_success_(false) {
+ DCHECK(package);
+}
+
+SaveItem::~SaveItem() {
+}
+
+// Set start state for save item.
+void SaveItem::Start() {
+ DCHECK(state_ == WAIT_START);
+ state_ = IN_PROGRESS;
+}
+
+// If we've received more data than we were expecting (bad server info?),
+// revert to 'unknown size mode'.
+void SaveItem::UpdateSize(int64 bytes_so_far) {
+ received_bytes_ = bytes_so_far;
+ if (received_bytes_ >= total_bytes_)
+ total_bytes_ = 0;
+}
+
+// Updates from the file thread may have been posted while this saving job
+// was being canceled in the UI thread, so we'll accept them unless we're
+// complete.
+void SaveItem::Update(int64 bytes_so_far) {
+ if (state_ != IN_PROGRESS) {
+ NOTREACHED();
+ return;
+ }
+ UpdateSize(bytes_so_far);
+}
+
+// Cancel this saving item job. If the job is not in progress, ignore
+// this command. The SavePackage will each in-progress SaveItem's cancel
+// when canceling whole saving page job.
+void SaveItem::Cancel() {
+ // If item is in WAIT_START mode, which means no request has been sent.
+ // So we need not to cancel it.
+ if (state_ != IN_PROGRESS) {
+ // Small downloads might be complete before method has a chance to run.
+ return;
+ }
+ state_ = CANCELED;
+ is_success_ = false;
+ Finish(received_bytes_, false);
+ package_->SaveCanceled(this);
+}
+
+// Set finish state for a save item
+void SaveItem::Finish(int64 size, bool is_success) {
+ // When this function is called, the SaveItem should be one of following
+ // three situations.
+ // a) The data of this SaveItem is finished saving. So it should have
+ // generated final name.
+ // b) Error happened before the start of saving process. So no |save_id_| is
+ // generated for this SaveItem and the |is_success_| should be false.
+ // c) Error happened in the start of saving process, the SaveItem has a save
+ // id, |is_success_| should be false, and the |size| should be 0.
+ DCHECK(has_final_name() || (save_id_ == -1 && !is_success_) ||
+ (save_id_ != -1 && !is_success_ && !size));
+ state_ = COMPLETE;
+ is_success_ = is_success;
+ UpdateSize(size);
+}
+
+// Calculate the percentage of the save item
+int SaveItem::PercentComplete() const {
+ switch (state_) {
+ case COMPLETE:
+ case CANCELED:
+ return 100;
+ case WAIT_START:
+ return 0;
+ case IN_PROGRESS: {
+ int percent = 0;
+ if (total_bytes_ > 0)
+ percent = static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
+ return percent;
+ }
+ default: {
+ NOTREACHED();
+ return -1;
+ }
+ }
+}
+
+// Rename the save item with new path.
+void SaveItem::Rename(const std::wstring& full_path) {
+ DCHECK(!full_path.empty() && !has_final_name());
+ full_path_ = full_path;
+ file_name_ = file_util::GetFilenameFromPath(full_path_);
+ has_final_name_ = true;
+}
+
+void SaveItem::SetSaveId(int32 save_id) {
+ DCHECK(save_id_ == -1);
+ save_id_ = save_id;
+}
+
+void SaveItem::SetTotalBytes(int64 total_bytes) {
+ DCHECK(total_bytes_ == 0);
+ total_bytes_ = total_bytes;
+}
+
diff --git a/chrome/browser/download/save_item.h b/chrome/browser/download/save_item.h
new file mode 100644
index 0000000..3c3ed4a
--- /dev/null
+++ b/chrome/browser/download/save_item.h
@@ -0,0 +1,112 @@
+// 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_SAVE_ITEM_H__
+#define CHROME_BROWSER_DOWNLOAD_SAVE_ITEM_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/download/save_types.h"
+
+class SavePackage;
+
+// One SaveItem per save file. This is the model class that stores all the
+// state for one save file.
+class SaveItem {
+ public:
+ enum SaveState {
+ WAIT_START,
+ IN_PROGRESS,
+ COMPLETE,
+ CANCELED
+ };
+
+ SaveItem(const std::wstring& url,
+ const std::wstring& referrer,
+ SavePackage* package,
+ SaveFileCreateInfo::SaveFileSource save_source);
+
+ ~SaveItem();
+
+ void Start();
+
+ // Received a new chunk of data.
+ void Update(int64 bytes_so_far);
+
+ // Cancel saving item.
+ void Cancel();
+
+ // Saving operation completed.
+ void Finish(int64 size, bool is_success);
+
+ // Rough percent complete, -1 means we don't know (since we didn't receive a
+ // total size).
+ int PercentComplete() const;
+
+ // Update path for SaveItem, the actual file is renamed on the file thread.
+ void Rename(const std::wstring& full_path);
+
+ void SetSaveId(int32 save_id);
+
+ void SetTotalBytes(int64 total_bytes);
+
+ // Accessors.
+ SaveState state() const { return state_; }
+ const std::wstring full_path() const { return full_path_; }
+ const std::wstring file_name() const { return file_name_; }
+ const std::wstring& url() const { return url_; }
+ const std::wstring& referrer() const { return referrer_; }
+ int64 total_bytes() const { return total_bytes_; }
+ int64 received_bytes() const { return received_bytes_; }
+ int32 save_id() const { return save_id_; }
+ bool has_final_name() const { return has_final_name_; }
+ bool success() const { return is_success_; }
+ SaveFileCreateInfo::SaveFileSource save_source() const {
+ return save_source_;
+ }
+ SavePackage* package() const { return package_; }
+
+ private:
+ // Internal helper for maintaining consistent received and total sizes.
+ void UpdateSize(int64 size);
+
+ // Request ID assigned by the ResourceDispatcherHost.
+ int32 save_id_;
+
+ // Full path to the save item file.
+ std::wstring full_path_;
+
+ // Short display version of the file.
+ std::wstring file_name_;
+
+ // The URL for this save item.
+ std::wstring url_;
+ std::wstring referrer_;
+
+ // Total bytes expected.
+ int64 total_bytes_;
+
+ // Current received bytes.
+ int64 received_bytes_;
+
+ // The current state of this save item.
+ SaveState state_;
+
+ // Specifies if this name is a final or not.
+ bool has_final_name_;
+
+ // Flag indicates whether SaveItem has error while in saving process.
+ bool is_success_;
+
+ SaveFileCreateInfo::SaveFileSource save_source_;
+
+ // Our owning object.
+ SavePackage* package_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SaveItem);
+};
+
+#endif // CHROME_BROWSER_DOWNLOAD_SAVE_ITEM_H__
+
diff --git a/chrome/browser/download/save_package.cc b/chrome/browser/download/save_package.cc
new file mode 100644
index 0000000..5410d31
--- /dev/null
+++ b/chrome/browser/download/save_package.cc
@@ -0,0 +1,1064 @@
+// 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/save_package.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "base/win_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/download/save_file.h"
+#include "chrome/browser/download/save_file_manager.h"
+#include "chrome/browser/download/save_page_model.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/render_process_host.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/render_view_host_delegate.h"
+#include "chrome/browser/resource_dispatcher_host.h"
+#include "chrome/browser/tab_util.h"
+#include "chrome/browser/web_contents.h"
+#include "chrome/browser/views/download_shelf_view.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/l10n_util.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 "net/base/net_util.h"
+#include "net/url_request/url_request_context.h"
+#include "webkit/glue/dom_serializer_delegate.h"
+
+#include "generated_resources.h"
+
+// Default name which will be used when we can not get proper name from
+// resource URL.
+static const wchar_t kDefaultSaveName[] = L"saved_resource";
+
+// Maximum number of file ordinal number. I think it's big enough for resolving
+// name-conflict files which has same base file name.
+static const int32 kMaxFileOrdinalNumber = 9999;
+
+// Maximum length for file path. Since Windows have MAX_PATH limitation for
+// file path, we need to make sure length of file path of every saved file
+// is less than MAX_PATH
+static const uint32 kMaxFilePathLength = MAX_PATH - 1;
+
+// Maximum length for file ordinal number part. Since we only support the
+// maximum 9999 for ordinal number, which means maximum file ordinal number part
+// should be "(9998)", so the value is 6.
+static const uint32 kMaxFileOrdinalNumberPartLength = 6;
+
+SavePackage::SavePackage(WebContents* web_content,
+ SavePackageType save_type,
+ const std::wstring& file_full_path,
+ const std::wstring& directory_full_path)
+ : web_contents_(web_content),
+ save_type_(save_type),
+ saved_main_file_path_(file_full_path),
+ saved_main_directory_path_(directory_full_path),
+ all_save_items_count_(0),
+ disk_error_occurred_(false),
+ user_canceled_(false),
+ download_(NULL),
+ finished_(false),
+ wait_state_(INITIALIZE) {
+ DCHECK(web_content);
+ const GURL& current_page_url = web_contents_->GetURL();
+ DCHECK(current_page_url.is_valid());
+ page_url_ = UTF8ToWide(current_page_url.spec());
+ DCHECK(save_type_ == SAVE_AS_ONLY_HTML ||
+ save_type_ == SAVE_AS_COMPLETE_HTML);
+ DCHECK(!saved_main_file_path_.empty() &&
+ saved_main_file_path_.length() <= kMaxFilePathLength);
+ DCHECK(!saved_main_directory_path_.empty() &&
+ saved_main_directory_path_.length() < kMaxFilePathLength);
+}
+
+// This is for testing use. Set |finished_| as true because we don't want
+// method Cancel to be be called in destructor in test mode.
+SavePackage::SavePackage(const wchar_t* file_full_path,
+ const wchar_t* directory_full_path)
+ : all_save_items_count_(0),
+ saved_main_file_path_(file_full_path),
+ saved_main_directory_path_(directory_full_path),
+ finished_(true),
+ download_(NULL),
+ user_canceled_(false),
+ disk_error_occurred_(false) {
+ DCHECK(!saved_main_file_path_.empty() &&
+ saved_main_file_path_.length() <= kMaxFilePathLength);
+ DCHECK(!saved_main_directory_path_.empty() &&
+ saved_main_directory_path_.length() < kMaxFilePathLength);
+}
+
+SavePackage::~SavePackage() {
+ // Stop receiving saving job's updates
+ if (!finished_ && !canceled()) {
+ // Unexpected quit.
+ Cancel(true);
+ }
+
+ DCHECK(all_save_items_count_ == (waiting_item_queue_.size() +
+ completed_count() +
+ in_process_count()));
+ // Free all SaveItems.
+ while (!waiting_item_queue_.empty()) {
+ // We still have some items which are waiting for start to save.
+ SaveItem* save_item = waiting_item_queue_.front();
+ waiting_item_queue_.pop();
+ delete save_item;
+ }
+
+ STLDeleteValues(&saved_success_items_);
+ STLDeleteValues(&in_progress_items_);
+ STLDeleteValues(&saved_failed_items_);
+
+ if (download_) {
+ // We call this to remove the view from the shelf. It will invoke
+ // DownloadManager::RemoveDownload, but since the fake DownloadItem is not
+ // owned by DownloadManager, it will do nothing to our fake item.
+ download_->Remove();
+ delete download_;
+ download_ = NULL;
+ }
+ file_manager_ = NULL;
+}
+
+// Cancel all in progress request, might be called by user or internal error.
+void SavePackage::Cancel(bool user_action) {
+ if (!canceled()) {
+ if (user_action)
+ user_canceled_ = true;
+ else
+ disk_error_occurred_ = true;
+ Stop();
+ }
+}
+
+// Initialize the SavePackage.
+bool SavePackage::Init() {
+ // Set proper running state.
+ if (wait_state_ != INITIALIZE)
+ return false;
+
+ wait_state_ = START_PROCESS;
+
+ // Initialize the request context and resource dispatcher.
+ Profile* profile = web_contents_->profile();
+ if (!profile) {
+ NOTREACHED();
+ return false;
+ }
+
+ request_context_ = profile->GetRequestContext();
+
+ ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
+ if (!rdh) {
+ NOTREACHED();
+ return false;
+ }
+
+ file_manager_ = rdh->save_file_manager();
+ if (!file_manager_) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Create the fake DownloadItem and display the view.
+ download_ = new DownloadItem(1, saved_main_file_path_,
+ page_url_, Time::Now(), 0, -1, -1);
+ download_->set_manager(web_contents_->profile()->GetDownloadManager());
+ DownloadShelfView* shelf = web_contents_->GetDownloadShelfView();
+ shelf->AddDownloadView(new DownloadItemView(
+ download_, shelf, new SavePageModel(this, download_)));
+ web_contents_->SetDownloadShelfVisible(true);
+
+ // Check save type and process the save page job.
+ if (save_type_ == SAVE_AS_COMPLETE_HTML) {
+ // Get directory
+ DCHECK(!saved_main_directory_path_.empty());
+ GetAllSavableResourceLinksForCurrentPage();
+ } else {
+ wait_state_ = NET_FILES;
+ GURL u(page_url_);
+ SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
+ SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
+ SaveFileCreateInfo::SAVE_FILE_FROM_NET;
+ SaveItem* save_item = new SaveItem(page_url_,
+ L"",
+ this,
+ save_source);
+ // Add this item to waiting list.
+ waiting_item_queue_.push(save_item);
+ all_save_items_count_ = 1;
+ download_->set_total_bytes(1);
+
+ DoSavingProcess();
+ }
+
+ return true;
+}
+
+// Generate name for saving resource.
+bool SavePackage::GenerateFilename(const std::string& disposition,
+ const std::wstring& url,
+ bool need_html_ext,
+ std::wstring* generated_name) {
+ std::wstring file_name =
+ net::GetSuggestedFilename(GURL(url), disposition, kDefaultSaveName);
+
+ DCHECK(!file_name.empty());
+ // Check whether we have same name before.
+ std::wstring::size_type last_dot = file_name.rfind(L'.');
+ std::wstring pure_file_name, file_name_ext;
+ if (last_dot == std::wstring::npos) {
+ pure_file_name = file_name;
+ } else {
+ pure_file_name = std::wstring(file_name, 0, last_dot);
+ file_name_ext = std::wstring(file_name, last_dot);
+ }
+ // If it is HTML resource, use ".htm" as its extension name.
+ if (need_html_ext)
+ file_name_ext = L".htm";
+ if (file_name_ext == L".")
+ file_name_ext.clear();
+
+ // Get safe pure file name.
+ if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
+ kMaxFilePathLength, &pure_file_name))
+ return false;
+
+ file_name = pure_file_name + file_name_ext;
+
+ // Check whether we already have same name.
+ if (file_name_set_.find(file_name) == file_name_set_.end()) {
+ file_name_set_.insert(file_name);
+ } else {
+ // Found same name, increase the ordinal number for the file name.
+ std::wstring base_file_name, file_ordinal_number;
+
+ if (!GetBaseFileNameAndFileOrdinalNumber(pure_file_name, &base_file_name,
+ &file_ordinal_number))
+ base_file_name = pure_file_name;
+
+ // We need to make sure the length of base file name plus maximum ordinal
+ // number path will be less than or equal to kMaxFilePathLength.
+ if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
+ kMaxFilePathLength - kMaxFileOrdinalNumberPartLength, &base_file_name))
+ return false;
+
+ // Prepare the new ordinal number.
+ uint32 ordinal_number;
+ FileNameCountMap::iterator it = file_name_count_map_.find(base_file_name);
+ if (it == file_name_count_map_.end()) {
+ // First base-name-conflict resolving, use 1 as initial ordinal number.
+ file_name_count_map_[base_file_name] = 1;
+ ordinal_number = 1;
+ } else {
+ // We have met same base-name conflict, use latest ordinal number.
+ ordinal_number = it->second;
+ }
+
+ if (ordinal_number > (kMaxFileOrdinalNumber - 1)) {
+ // Use a random file from temporary file.
+ file_util::CreateTemporaryFileName(&file_name);
+ file_name = file_util::GetFilenameFromPath(file_name);
+ // Get safe pure file name.
+ if (!GetSafePureFileName(saved_main_directory_path_, std::wstring(),
+ kMaxFilePathLength, &file_name))
+ return false;
+ } else {
+ uint32 i;
+ for (i = ordinal_number; i < kMaxFileOrdinalNumber; ++i) {
+ std::wstring new_name =
+ StringPrintf(L"%ls(%d)", base_file_name.c_str(), i) + file_name_ext;
+ if (file_name_set_.find(new_name) == file_name_set_.end()) {
+ // Resolved name conflict.
+ file_name = new_name;
+ file_name_count_map_[base_file_name] = ++i;
+ break;
+ }
+ }
+ }
+
+ file_name_set_.insert(file_name);
+ }
+
+ DCHECK(!file_name.empty());
+ generated_name->assign(file_name);
+
+ return true;
+}
+
+// We have received a message from SaveFileManager about a new saving job. We
+// create a SaveItem and store it in our in_progress list.
+void SavePackage::StartSave(const SaveFileCreateInfo* info) {
+ DCHECK(info && !info->url.empty());
+
+ SaveUrlItemMap::iterator it = in_progress_items_.find(info->url);
+ if (it == in_progress_items_.end()) {
+ // If not found, we must have cancel action.
+ DCHECK(canceled());
+ return;
+ }
+ SaveItem* save_item = it->second;
+
+ DCHECK(!saved_main_file_path_.empty());
+
+ save_item->SetSaveId(info->save_id);
+ save_item->SetTotalBytes(info->total_bytes);
+
+ // Determine the proper path for a saving job, by choosing either the default
+ // save directory, or prompting the user.
+ DCHECK(!save_item->has_final_name());
+ if (info->url != page_url_) {
+ std::wstring generated_name;
+ // For HTML resource file, make sure it will have .htm as extension name,
+ // otherwise, when you open the saved page in Chrome again, download
+ // file manager will treat it as downloadable resource, and download it
+ // instead of opening it as HTML.
+ bool need_html_ext =
+ info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM;
+ if (!GenerateFilename(info->content_disposition,
+ info->url,
+ need_html_ext,
+ &generated_name)) {
+ // We can not generate file name for this SaveItem, so we cancel the
+ // saving page job if the save source is from serialized DOM data.
+ // Otherwise, it means this SaveItem is sub-resource type, we treat it
+ // as an error happened on saving. We can ignore this type error for
+ // sub-resource links which will be resolved as absolute links instead
+ // of local links in final saved contents.
+ if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM)
+ Cancel(true);
+ else
+ SaveFinished(save_item->save_id(), 0, false);
+ return;
+ }
+
+ // When saving page as only-HTML, we only have a SaveItem whose url
+ // must be page_url_.
+ DCHECK(save_type_ == SAVE_AS_COMPLETE_HTML);
+ DCHECK(!saved_main_directory_path_.empty());
+
+ // Now we get final name retrieved from GenerateFilename, we will use it
+ // rename the SaveItem.
+ std::wstring final_name = saved_main_directory_path_;
+ file_util::AppendToPath(&final_name, generated_name);
+ save_item->Rename(final_name);
+ } else {
+ // It is the main HTML file, use the name chosen by the user.
+ save_item->Rename(saved_main_file_path_);
+ }
+
+ // If the save source is from file system, inform SaveFileManager to copy
+ // corresponding file to the file path which this SaveItem specifies.
+ if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) {
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::SaveLocalFile,
+ save_item->url(),
+ save_item->save_id(),
+ GetTabId()));
+ return;
+ }
+
+ // Check whether we begin to require serialized HTML data.
+ if (save_type_ == SAVE_AS_COMPLETE_HTML && wait_state_ == HTML_DATA) {
+ // Inform backend to serialize the all frames' DOM and send serialized
+ // HTML data back.
+ GetSerializedHtmlDataForCurrentPageWithLocalLinks();
+ }
+}
+
+// Look up SaveItem by save id from in progress map.
+SaveItem* SavePackage::LookupItemInProcessBySaveId(int32 save_id) {
+ if (in_process_count()) {
+ SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ for (; it != in_progress_items_.end(); ++it) {
+ SaveItem* save_item = it->second;
+ DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
+ if (save_item->save_id() == save_id)
+ return save_item;
+ }
+ }
+ return NULL;
+}
+
+// Remove SaveItem from in progress map and put it to saved map.
+void SavePackage::PutInProgressItemToSavedMap(SaveItem* save_item) {
+ SaveUrlItemMap::iterator it = in_progress_items_.find(
+ save_item->url());
+ DCHECK(it != in_progress_items_.end());
+ DCHECK(save_item == it->second);
+ in_progress_items_.erase(it);
+
+ if (save_item->success()) {
+ // Add it to saved_success_items_.
+ DCHECK(saved_success_items_.find(save_item->save_id()) ==
+ saved_success_items_.end());
+ saved_success_items_[save_item->save_id()] = save_item;
+ } else {
+ // Add it to saved_failed_items_.
+ DCHECK(saved_failed_items_.find(save_item->url()) ==
+ saved_failed_items_.end());
+ saved_failed_items_[save_item->url()] = save_item;
+ }
+}
+
+// Called for updating saving state.
+bool SavePackage::UpdateSaveProgress(int32 save_id,
+ int64 size,
+ bool write_success) {
+ // Because we might have canceled this saving job before,
+ // so we might not find corresponding SaveItem.
+ SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
+ if (!save_item)
+ return false;
+
+ save_item->Update(size);
+
+ // If we got disk error, cancel whole save page job.
+ if (!write_success) {
+ // Cancel job with reason of disk error.
+ Cancel(false);
+ }
+ return true;
+}
+
+// Stop all page saving jobs that are in progress and instruct the file thread
+// to delete all saved files.
+void SavePackage::Stop() {
+ // When stopping, if it still has some items in in_progress, cancel them.
+ DCHECK(canceled());
+ if (in_process_count()) {
+ SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ for (; it != in_progress_items_.end(); ++it) {
+ SaveItem* save_item = it->second;
+ DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
+ save_item->Cancel();
+ }
+ // Remove all in progress item to saved map. For failed items, they will
+ // be put into saved_failed_items_, for successful item, they will be put
+ // into saved_success_items_.
+ while (in_process_count())
+ PutInProgressItemToSavedMap(in_progress_items_.begin()->second);
+ }
+
+ // This vector contains the save ids of the save files which SaveFileManager
+ // needs to remove from its save_file_map_.
+ SaveIDList save_ids;
+ for (SavedItemMap::iterator it = saved_success_items_.begin();
+ it != saved_success_items_.end(); ++it)
+ save_ids.push_back(it->first);
+ for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
+ it != saved_failed_items_.end(); ++it)
+ save_ids.push_back(it->second->save_id());
+
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::RemoveSavedFileFromFileMap,
+ save_ids));
+
+ finished_ = true;
+ wait_state_ = FAILED;
+
+ // Inform the DownloadItem we have canceled whole save page job.
+ DCHECK(download_);
+ download_->Cancel(false);
+}
+
+void SavePackage::CheckFinish() {
+ if (in_process_count() || finished_)
+ return;
+
+ std::wstring dir = save_type_ == SAVE_AS_COMPLETE_HTML ?
+ saved_main_directory_path_ :
+ L"";
+
+ // This vector contains the final names of all the successfully saved files
+ // along with their save ids. It will be passed to SaveFileManager to do the
+ // renaming job.
+ FinalNameList final_names;
+ for (SavedItemMap::iterator it = saved_success_items_.begin();
+ it != saved_success_items_.end(); ++it)
+ final_names.push_back(std::make_pair(it->first,
+ it->second->full_path()));
+
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::RenameAllFiles,
+ final_names,
+ dir,
+ web_contents_->process()->host_id(),
+ web_contents_->render_view_host()->routing_id()));
+}
+
+// Successfully finished all items of this SavePackage.
+void SavePackage::Finish() {
+ // User may cancel the job when we're moving files to the final directory.
+ if (canceled())
+ return;
+
+ wait_state_ = SUCCESSFUL;
+ finished_ = true;
+
+ // This vector contains the save ids of the save files which SaveFileManager
+ // needs to remove from its save_file_map_.
+ SaveIDList save_ids;
+ for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
+ it != saved_failed_items_.end(); ++it)
+ save_ids.push_back(it->second->save_id());
+
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::RemoveSavedFileFromFileMap,
+ save_ids));
+
+ DCHECK(download_);
+ download_->Finished(all_save_items_count_);
+}
+
+// Called for updating end state.
+void SavePackage::SaveFinished(int32 save_id, int64 size, bool is_success) {
+ // Because we might have canceled this saving job before,
+ // so we might not find corresponding SaveItem. Just ignore it.
+ SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
+ if (!save_item)
+ return;
+
+ // Let SaveItem set end state.
+ save_item->Finish(size, is_success);
+ // Remove the associated save id and SavePackage.
+ file_manager_->RemoveSaveFile(save_id, save_item->url(), this);
+
+ PutInProgressItemToSavedMap(save_item);
+
+ // Inform the DownloadItem to update UI.
+ DCHECK(download_);
+ // We use the received bytes as number of saved files.
+ download_->Update(completed_count());
+
+ if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM &&
+ save_item->url() == page_url_ && !save_item->received_bytes()) {
+ // If size of main HTML page is 0, treat it as disk error.
+ Cancel(false);
+ return;
+ }
+
+ if (canceled()) {
+ DCHECK(finished_);
+ return;
+ }
+
+ // Continue processing the save page job.
+ DoSavingProcess();
+
+ // Check whether we can successfully finish whole job.
+ CheckFinish();
+}
+
+// Sometimes, the net io will only call SaveFileManager::SaveFinished with
+// save id -1 when it encounters error. Since in this case, save id will be
+// -1, so we can only use URL to find which SaveItem is associated with
+// this error.
+// Saving an item failed. If it's a sub-resource, ignore it. If the error comes
+// from serializing HTML data, then cancel saving page.
+void SavePackage::SaveFailed(const std::wstring& save_url) {
+ SaveUrlItemMap::iterator it = in_progress_items_.find(save_url);
+ if (it == in_progress_items_.end()) {
+ NOTREACHED(); // Should not exist!
+ return;
+ }
+ SaveItem* save_item = it->second;
+
+ save_item->Finish(0, false);
+
+ PutInProgressItemToSavedMap(save_item);
+
+ // Inform the DownloadItem to update UI.
+ DCHECK(download_);
+ // We use the received bytes as number of saved files.
+ download_->Update(completed_count());
+
+ if (save_type_ == SAVE_AS_ONLY_HTML ||
+ save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
+ // We got error when saving page. Treat it as disk error.
+ Cancel(true);
+ }
+
+ if (canceled()) {
+ DCHECK(finished_);
+ return;
+ }
+
+ // Continue processing the save page job.
+ DoSavingProcess();
+
+ CheckFinish();
+}
+
+void SavePackage::SaveCanceled(SaveItem* save_item) {
+ // Call the RemoveSaveFile in UI thread.
+ file_manager_->RemoveSaveFile(save_item->save_id(),
+ save_item->url(),
+ this);
+ if (save_item->save_id() != -1)
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::CancelSave,
+ save_item->save_id()));
+}
+
+// Initiate a saving job of a specific URL. We send the request to
+// SaveFileManager, which will dispatch it to different approach according to
+// the save source. Parameter process_all_remaining_items indicates whether
+// we need to save all remaining items.
+void SavePackage::SaveNextFile(bool process_all_remaining_items) {
+ DCHECK(web_contents_);
+ DCHECK(waiting_item_queue_.size());
+
+ do {
+ // Pop SaveItem from waiting list.
+ SaveItem* save_item = waiting_item_queue_.front();
+ waiting_item_queue_.pop();
+
+ // Add the item to in_progress_items_.
+ SaveUrlItemMap::iterator it = in_progress_items_.find(
+ save_item->url());
+ DCHECK(it == in_progress_items_.end());
+ in_progress_items_[save_item->url()] = save_item;
+ save_item->Start();
+ file_manager_->SaveURL(save_item->url(),
+ save_item->referrer(),
+ web_contents_->process()->host_id(),
+ web_contents_->render_view_host()->routing_id(),
+ save_item->save_source(),
+ save_item->full_path(),
+ request_context_.get(),
+ this);
+ } while (process_all_remaining_items && waiting_item_queue_.size());
+}
+
+
+// Open download page in windows explorer on file thread, to avoid blocking the
+// user interface.
+void SavePackage::ShowDownloadInShell() {
+ DCHECK(file_manager_);
+ DCHECK(finished_ && !canceled() && !saved_main_file_path_.empty());
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::OnShowSavedFileInShell,
+ saved_main_file_path_));
+}
+
+// Calculate the percentage of whole save page job.
+int SavePackage::PercentComplete() {
+ if (!all_save_items_count_)
+ return 0;
+ else if (!in_process_count())
+ return 100;
+ else
+ return completed_count() / all_save_items_count_;
+}
+
+// Continue processing the save page job after one SaveItem has been
+// finished.
+void SavePackage::DoSavingProcess() {
+ if (save_type_ == SAVE_AS_COMPLETE_HTML) {
+ // We guarantee that images and JavaScripts must be downloaded first.
+ // So when finishing all those sub-resources, we will know which
+ // sub-resource's link can be replaced with local file path, which
+ // sub-resource's link need to be replaced with absolute URL which
+ // point to its internet address because it got error when saving its data.
+ SaveItem* save_item = NULL;
+ // Start a new SaveItem job if we still have job in waiting queue.
+ if (waiting_item_queue_.size()) {
+ DCHECK(wait_state_ == NET_FILES);
+ save_item = waiting_item_queue_.front();
+ if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
+ SaveNextFile(false);
+ } else if (!in_process_count()) {
+ // If there is no in-process SaveItem, it means all sub-resources
+ // have been processed. Now we need to start serializing HTML DOM
+ // for the current page to get the generated HTML data.
+ wait_state_ = HTML_DATA;
+ // All non-HTML resources have been finished, start all remaining
+ // HTML files.
+ SaveNextFile(true);
+ }
+ } else if (in_process_count()) {
+ // Continue asking for HTML data.
+ DCHECK(wait_state_ == HTML_DATA);
+ }
+ } else {
+ // Save as HTML only.
+ DCHECK(wait_state_ == NET_FILES);
+ DCHECK(save_type_ == SAVE_AS_ONLY_HTML);
+ if (waiting_item_queue_.size()) {
+ DCHECK(all_save_items_count_ == waiting_item_queue_.size());
+ SaveNextFile(false);
+ }
+ }
+}
+
+int SavePackage::GetTabId() {
+ DCHECK(web_contents_);
+ return web_contents_->process()->host_id();
+}
+
+// After finishing all SaveItems which need to get data from net.
+// We collect all URLs which have local storage and send the
+// map:(originalURL:currentLocalPath) to render process (backend).
+// Then render process will serialize DOM and send data to us.
+void SavePackage::GetSerializedHtmlDataForCurrentPageWithLocalLinks() {
+ if (wait_state_ != HTML_DATA)
+ return;
+ std::vector<std::wstring> saved_links;
+ std::vector<std::wstring> saved_file_paths;
+ int successful_started_items_count = 0;
+
+ // Collect all saved items which have local storage.
+ // First collect the status of all the resource files and check whether they
+ // have created local files although they have not been completely saved.
+ // If yes, the file can be saved. Otherwise, there is a disk error, so we
+ // need to cancel the page saving job.
+ for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ it != in_progress_items_.end(); ++it) {
+ DCHECK(it->second->save_source() ==
+ SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
+ if (it->second->has_final_name())
+ successful_started_items_count++;
+ saved_links.push_back(it->second->url());
+ saved_file_paths.push_back(it->second->file_name());
+ }
+
+ // If not all file of HTML resource have been started, then wait.
+ if (successful_started_items_count != in_process_count())
+ return;
+
+ // Collect all saved success items.
+ for (SavedItemMap::iterator it = saved_success_items_.begin();
+ it != saved_success_items_.end(); ++it) {
+ DCHECK(it->second->has_final_name());
+ saved_links.push_back(it->second->url());
+ saved_file_paths.push_back(it->second->file_name());
+ }
+
+ // Get the relative directory name.
+ std::wstring::size_type last_slash = saved_main_directory_path_.rfind(L'\\');
+ DCHECK(last_slash != std::wstring::npos);
+ std::wstring relative_dir_name = std::wstring(saved_main_directory_path_,
+ last_slash + 1);
+
+ relative_dir_name = std::wstring(L"./") + relative_dir_name + L"/";
+
+ web_contents_->GetSerializedHtmlDataForCurrentPageWithLocalLinks(
+ saved_links, saved_file_paths, relative_dir_name);
+}
+
+// Process the serialized HTML content data of a specified web page
+// retrieved from render process.
+void SavePackage::ProcessSerializedHtmlData(const GURL& frame_url,
+ const std::string& data,
+ int32 status) {
+ webkit_glue::DomSerializerDelegate::PageSavingSerializationStatus flag =
+ static_cast<webkit_glue::DomSerializerDelegate::PageSavingSerializationStatus>
+ (status);
+ // Check current state.
+ if (wait_state_ != HTML_DATA)
+ return;
+
+ int tab_id = GetTabId();
+ // If the all frames are finished saving, we need to close the
+ // remaining SaveItems.
+ if (flag == webkit_glue::DomSerializerDelegate::ALL_FRAMES_ARE_FINISHED) {
+ for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
+ it != in_progress_items_.end(); ++it) {
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::SaveFinished,
+ it->second->save_id(),
+ it->second->url(),
+ tab_id,
+ true));
+ }
+ return;
+ }
+
+ std::wstring current_frame_url = UTF8ToWide(frame_url.spec());
+ SaveUrlItemMap::iterator it = in_progress_items_.find(current_frame_url);
+ if (it == in_progress_items_.end())
+ return;
+ SaveItem* save_item = it->second;
+ DCHECK(save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
+
+ if (!data.empty()) {
+ // Prepare buffer for saving HTML data.
+ char* new_data = static_cast<char*>(new char[data.size()]);
+ memcpy(new_data, data.data(), data.size());
+
+ // Call write file functionality in file thread.
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::UpdateSaveProgress,
+ save_item->save_id(),
+ new_data,
+ static_cast<int>(data.size())));
+ }
+
+ // Current frame is completed saving, call finish in file thread.
+ if (flag == webkit_glue::DomSerializerDelegate::CURRENT_FRAME_IS_FINISHED) {
+ file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
+ NewRunnableMethod(file_manager_,
+ &SaveFileManager::SaveFinished,
+ save_item->save_id(),
+ save_item->url(),
+ tab_id,
+ true));
+ }
+}
+
+// Ask for all savable resource links from backend, include main frame and
+// sub-frame.
+void SavePackage::GetAllSavableResourceLinksForCurrentPage() {
+ if (wait_state_ != START_PROCESS)
+ return;
+
+ wait_state_ = RESOURCES_LIST;
+ GURL main_page_url(page_url_);
+ web_contents_->GetAllSavableResourceLinksForCurrentPage(main_page_url);
+}
+
+// Give backend the lists which contain all resource links that have local
+// storage, after which, render process will serialize DOM for generating
+// HTML data.
+void SavePackage::ProcessCurrentPageAllSavableResourceLinks(
+ const std::vector<GURL>& resources_list,
+ const std::vector<GURL>& referrers_list,
+ const std::vector<GURL>& frames_list) {
+ if (wait_state_ != RESOURCES_LIST)
+ return;
+
+ DCHECK(resources_list.size() == referrers_list.size());
+ all_save_items_count_ = static_cast<int>(resources_list.size()) +
+ static_cast<int>(frames_list.size());
+
+ // We use total bytes as the total number of files we want to save.
+ download_->set_total_bytes(all_save_items_count_);
+
+ if (all_save_items_count_) {
+ // Put all sub-resources to wait list.
+ for (int i = 0; i < static_cast<int>(resources_list.size()); ++i) {
+ const GURL& u = resources_list[i];
+ DCHECK(u.is_valid());
+ SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
+ SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
+ SaveFileCreateInfo::SAVE_FILE_FROM_NET;
+ SaveItem* save_item = new SaveItem(UTF8ToWide(u.spec()),
+ UTF8ToWide(referrers_list[i].spec()), this, save_source);
+ waiting_item_queue_.push(save_item);
+ }
+ // Put all HTML resources to wait list.
+ for (int i = 0; i < static_cast<int>(frames_list.size()); ++i) {
+ const GURL& u = frames_list[i];
+ DCHECK(u.is_valid());
+ SaveItem* save_item = new SaveItem(UTF8ToWide(u.spec()), L"",
+ this, SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
+ waiting_item_queue_.push(save_item);
+ }
+ wait_state_ = NET_FILES;
+ DoSavingProcess();
+ } else {
+ // No resource files need to be saved, treat it as user cancel.
+ Cancel(true);
+ }
+}
+
+std::wstring SavePackage::GetSuggestNameForSaveAs(PrefService* prefs,
+ const std::wstring& name) {
+ // Check whether the preference has the preferred directory for saving file.
+ // If not, initialize it with default directory.
+ if (!prefs->IsPrefRegistered(prefs::kSaveFileDefaultDirectory)) {
+ std::wstring default_save_path;
+ PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_save_path);
+ file_util::AppendToPath(&default_save_path,
+ l10n_util::GetString(IDS_DOWNLOAD_DIRECTORY));
+ prefs->RegisterStringPref(prefs::kSaveFileDefaultDirectory,
+ default_save_path);
+ }
+
+ // Get the directory from preference.
+ StringPrefMember save_file_path;
+ save_file_path.Init(prefs::kSaveFileDefaultDirectory, prefs, NULL);
+ DCHECK(!(*save_file_path).empty());
+
+ // Ask user for getting final saving name.
+ std::wstring suggest_name, file_name;
+
+ file_name = name;
+ file_util::ReplaceIllegalCharacters(&file_name, L' ');
+ suggest_name = *save_file_path;
+ file_util::AppendToPath(&suggest_name, file_name);
+
+ return suggest_name;
+}
+
+// Static.
+bool SavePackage::GetSaveInfo(const std::wstring& suggest_name,
+ HWND container_hwnd,
+ SavePackageParam* param) {
+ // Use "Web Page, Complete" option as default choice of saving page.
+ unsigned index = 2;
+
+ // If the conetnts can not be saved as complete-HTML, do not show the
+ // file filters.
+ if (CanSaveAsComplete(param->current_tab_mime_type)) {
+ // Create filter string.
+ std::wstring filter = l10n_util::GetString(IDS_SAVE_PAGE_FILTER);
+ filter.resize(filter.size() + 2);
+ filter[filter.size() - 1] = L'\0';
+ filter[filter.size() - 2] = L'\0';
+
+ if (!win_util::SaveFileAsWithFilter(container_hwnd,
+ suggest_name, filter.c_str(), L"htm",
+ &index, &param->saved_main_file_path))
+ return false;
+ } else {
+ if (!win_util::SaveFileAs(container_hwnd, suggest_name,
+ &param->saved_main_file_path))
+ return false;
+ // Set save-as type to only-HTML if the contents of current tab can not be
+ // saved as complete-HTML.
+ index = 1;
+ }
+ // The option index is not zero-based.
+ DCHECK(index > 0 && index < 3);
+ param->dir = file_util::GetDirectoryFromPath(param->saved_main_file_path);
+
+ StringPrefMember save_file_path;
+ save_file_path.Init(prefs::kSaveFileDefaultDirectory, param->prefs, NULL);
+ // If user change the default saving directory, we will remember it just
+ // like IE and FireFox.
+ if (save_file_path.GetValue() != param->dir)
+ save_file_path.SetValue(param->dir);
+
+ param->save_type = (index == 1) ? SavePackage::SAVE_AS_ONLY_HTML :
+ SavePackage::SAVE_AS_COMPLETE_HTML;
+
+ if (param->save_type == SavePackage::SAVE_AS_COMPLETE_HTML) {
+ // Make new directory for saving complete file.
+ std::wstring file_name =
+ file_util::GetFilenameFromPath(param->saved_main_file_path);
+ std::wstring::size_type last_dot = file_name.rfind(L'.');
+ std::wstring pure_file_name;
+ if (last_dot == std::wstring::npos)
+ pure_file_name = file_name;
+ else
+ pure_file_name = std::wstring(file_name, 0, last_dot);
+ pure_file_name += L"_files";
+ file_util::AppendToPath(&param->dir, pure_file_name);
+ }
+
+ return true;
+}
+
+// Static.
+bool SavePackage::GetBaseFileNameAndFileOrdinalNumber(
+ const std::wstring& file_name,
+ std::wstring* base_file_name,
+ std::wstring* file_ordinal_number) {
+ if (file_name.empty() || !base_file_name || !file_ordinal_number)
+ return false;
+
+ // Find dot position.
+ std::wstring::size_type dot_position = file_name.rfind(L".");
+ // Find position of right parenthesis.
+ std::wstring::size_type parenthesis_right;
+ if (std::wstring::npos == dot_position)
+ parenthesis_right = file_name.rfind(L')');
+ else
+ parenthesis_right = dot_position - 1;
+ // The latest character of pure file name is not ")", return false.
+ if (std::wstring::npos == parenthesis_right)
+ return false;
+ if (file_name.at(parenthesis_right) != L')')
+ return false;
+ // Find position of left parenthesis.
+ std::wstring::size_type parenthesis_left = file_name.rfind(L'(');
+ if (std::wstring::npos == parenthesis_left)
+ return false;
+
+ if (parenthesis_right <= parenthesis_left)
+ return false;
+ // Check whether content between left parenthesis and right parenthesis is
+ // numeric or not.
+ std::wstring ordinal_number(file_name, parenthesis_left + 1,
+ parenthesis_right - parenthesis_left - 1);
+ for (std::wstring::const_iterator cit = ordinal_number.begin();
+ cit != ordinal_number.end(); ++cit)
+ if (!IsAsciiDigit(*cit))
+ return false;
+
+ *base_file_name = std::wstring(file_name, 0, parenthesis_left);
+ *file_ordinal_number = ordinal_number;
+ return true;
+}
+
+// Static
+bool SavePackage::IsSavableURL(const GURL& url) {
+ return url.SchemeIs("http") || url.SchemeIs("https") ||
+ url.SchemeIs("file") || url.SchemeIs("ftp");
+}
+
+// Static
+bool SavePackage::IsSavableContents(const std::string& contents_mime_type) {
+ // WebKit creates Document object when MIME type is application/xhtml+xml,
+ // so we also support this MIME type.
+ return contents_mime_type == "text/html" ||
+ contents_mime_type == "text/xml" ||
+ contents_mime_type == "application/xhtml+xml" ||
+ contents_mime_type == "text/plain";
+}
+
+// Static
+bool SavePackage::CanSaveAsComplete(const std::string& contents_mime_type) {
+ return contents_mime_type == "text/html";
+}
+
+// Static
+bool SavePackage::GetSafePureFileName(const std::wstring& dir_path,
+ const std::wstring& file_name_ext,
+ uint32 max_file_path_len,
+ std::wstring* pure_file_name) {
+ DCHECK(!pure_file_name->empty());
+ std::wstring final_name = dir_path;
+ file_util::AppendToPath(&final_name, *pure_file_name);
+ // Get total length of dir path, including ending "\".
+ const std::wstring::size_type dir_path_length =
+ final_name.length() - pure_file_name->length();
+ // Get available length for putting dir path and pure file name.
+ const std::wstring::size_type available_length =
+ static_cast<std::wstring::size_type>(max_file_path_len) -
+ file_name_ext.length();
+
+ if (final_name.length() <= available_length)
+ return true;
+
+ if (available_length > dir_path_length) {
+ *pure_file_name =
+ pure_file_name->substr(0, available_length - dir_path_length);
+ return true;
+ } else {
+ pure_file_name->clear();
+ return false;
+ }
+}
+
diff --git a/chrome/browser/download/save_package.h b/chrome/browser/download/save_package.h
new file mode 100644
index 0000000..8c12190
--- /dev/null
+++ b/chrome/browser/download/save_package.h
@@ -0,0 +1,309 @@
+// 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 SavePackage object manages the process of saving a page as only-html or
+// complete-html and providing the information for displaying saving status.
+// Saving page as only-html means means that we save web page to a single HTML
+// file regardless internal sub resources and sub frames.
+// Saving page as complete-html page means we save not only the main html file
+// the user told it to save but also a directory for the auxiliary files such
+// as all sub-frame html files, image files, css files and js files.
+//
+// Each page saving job may include one or multiple files which need to be
+// saved. Each file is represented by a SaveItem, and all SaveItems are owned
+// by the SavePackage. SaveItems are created when a user initiates a page
+// saving job, and exist for the duration of one tab's life time.
+
+#ifndef CHROME_BROWSER_DOWNLOAD_SAVE_PACKAGE_H__
+#define CHROME_BROWSER_DOWNLOAD_SAVE_PACKAGE_H__
+
+#include <string>
+#include <vector>
+#include <queue>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/hash_tables.h"
+#include "base/ref_counted.h"
+#include "base/time.h"
+#include "chrome/common/pref_member.h"
+#include "chrome/browser/download/save_item.h"
+#include "chrome/browser/download/save_types.h"
+
+class SaveFileManager;
+class SavePackage;
+class DownloadItem;
+class GURL;
+class MessageLoop;
+class PrefService;
+class Profile;
+class WebContents;
+class URLRequestContext;
+class WebContents;
+class Time;
+
+namespace base {
+class Thread;
+}
+
+// save package: manages all save item.
+class SavePackage : public base::RefCountedThreadSafe<SavePackage> {
+ public:
+ enum SavePackageType {
+ // User chose to save only the HTML of the page.
+ SAVE_AS_ONLY_HTML = 0,
+ // User chose to save complete-html page.
+ SAVE_AS_COMPLETE_HTML = 1
+ };
+
+ enum WaitState {
+ // State when created but not initialized.
+ INITIALIZE = 0,
+ // State when after initializing, but not yet saving.
+ START_PROCESS,
+ // Waiting on a list of savable resources from the backend.
+ RESOURCES_LIST,
+ // Waiting for data sent from net IO or from file system.
+ NET_FILES,
+ // Waiting for html DOM data sent from render process.
+ HTML_DATA,
+ // Saving page finished successfully.
+ SUCCESSFUL,
+ // Failed to save page.
+ FAILED
+ };
+
+ SavePackage(WebContents* web_content,
+ SavePackageType save_type,
+ const std::wstring& file_full_path,
+ const std::wstring& directory_full_path);
+
+ ~SavePackage();
+
+ // Initialize the SavePackage. Returns true if it initializes properly.
+ // Need to make sure that this method must be called in the UI thread because
+ // using g_browser_process on a non-UI thread can cause crashes during
+ // shutdown.
+ bool Init();
+
+ void Cancel(bool user_action);
+
+ void Finish();
+
+ // Notifications sent from the file thread to the UI thread.
+ void StartSave(const SaveFileCreateInfo* info);
+ bool UpdateSaveProgress(int32 save_id, int64 size, bool write_success);
+ void SaveFinished(int32 save_id, int64 size, bool is_success);
+ void SaveFailed(const std::wstring& save_url);
+ void SaveCanceled(SaveItem* save_item);
+
+ // Process current page's all savable links of sub resources, resources'
+ // referrer and frames(include main frame and sub frames) gotten from
+ // render process.
+ void ProcessCurrentPageAllSavableResourceLinks(
+ const std::vector<GURL>& resources_list,
+ const std::vector<GURL>& referrers_list,
+ const std::vector<GURL>& frames_list);
+
+ // Process the serialized html content data of a specified web page
+ // gotten from render process.
+ void ProcessSerializedHtmlData(const GURL& frame_url,
+ const std::string& data,
+ int32 status);
+
+ // Rough percent complete, -1 means we don't know (since we didn't receive a
+ // total size).
+ int PercentComplete();
+
+ // Show or Open a saved page via the Windows shell.
+ void ShowDownloadInShell();
+
+ bool canceled() { return user_canceled_ || disk_error_occurred_; }
+
+ // Accessor
+ bool finished() { return finished_; }
+ SavePackageType save_type() { return save_type_; }
+
+ // Since for one tab, it can only have one SavePackage in same time.
+ // Now we actually use render_process_id as tab's unique id.
+ int GetTabId();
+
+ // Helper function for preparing suggested name for the SaveAs Dialog. The
+ // suggested name is composed of the default save path and the web document's
+ // title.
+ static std::wstring GetSuggestNameForSaveAs(PrefService* prefs,
+ const std::wstring& name);
+
+ // This structure is for storing parameters which we will use to create
+ // a SavePackage object later.
+ struct SavePackageParam {
+ // MIME type of current tab contents.
+ const std::string& current_tab_mime_type;
+ // Pointer to preference service.
+ PrefService* prefs;
+ // Type about saving page as only-html or complete-html.
+ SavePackageType save_type;
+ // File path for main html file.
+ std::wstring saved_main_file_path;
+ // Directory path for saving sub resources and sub html frames.
+ std::wstring dir;
+
+ SavePackageParam(const std::string& mime_type)
+ : current_tab_mime_type(mime_type) { }
+ };
+ static bool GetSaveInfo(const std::wstring& suggest_name,
+ HWND container_hwnd,
+ SavePackageParam* param);
+
+ // File name is consist of pure file name, dot and file extension name. File
+ // name might has no dot and file extension, or has multiple dot inside file
+ // name. The dot, which separates the pure file name and file extension name,
+ // is last dot in the file name. If the file name matches following patterns:
+ // base_file_name(ordinal_number) or base_file_name(ordinal_number).extension,
+ // this function will return true and get the base file name part and
+ // ordinal_number part via output parameters. The |file_ordinal_number| could
+ // be empty if there is no content in ordinal_number part. If the file name
+ // does not match the pattern or the ordinal_number part has non-digit
+ // content, just return false.
+ static bool GetBaseFileNameAndFileOrdinalNumber(
+ const std::wstring& file_name,
+ std::wstring* base_file_name,
+ std::wstring* file_ordinal_number);
+
+ // Check whether we can do the saving page operation for the specified URL.
+ static bool IsSavableURL(const GURL& url);
+
+ // Check whether we can do the saving page operation for the contents which
+ // have the specified MIME type.
+ static bool IsSavableContents(const std::string& contents_mime_type);
+
+ // Check whether we can save page as complete-HTML for the contents which
+ // have specified a MIME type. Now only contents which have the MIME type
+ // "text/html" can be saved as complete-HTML.
+ static bool CanSaveAsComplete(const std::string& contents_mime_type);
+
+ // File name is considered being consist of pure file name, dot and file
+ // extension name. File name might has no dot and file extension, or has
+ // multiple dot inside file name. The dot, which separates the pure file
+ // name and file extension name, is last dot in the whole file name.
+ // This function is for making sure the length of specified file path is not
+ // great than the specified maximum length of file path and getting safe pure
+ // file name part if the input pure file name is too long.
+ // The parameter |dir_path| specifies directory part of the specified
+ // file path. The parameter |file_name_ext| specifies file extension
+ // name part of the specified file path (including start dot). The parameter
+ // |max_file_path_len| specifies maximum length of the specified file path.
+ // The parameter |pure_file_name| input pure file name part of the specified
+ // file path. If the length of specified file path is great than
+ // |max_file_path_len|, the |pure_file_name| will output new pure file name
+ // part for making sure the length of specified file path is less than
+ // specified maximum length of file path. Return false if the function can
+ // not get a safe pure file name, otherwise it returns true.
+ static bool GetSafePureFileName(const std::wstring& dir_path,
+ const std::wstring& file_name_ext,
+ uint32 max_file_path_len,
+ std::wstring* pure_file_name);
+
+ private:
+ // For testing.
+ friend class SavePackageTest;
+ SavePackage(const wchar_t* file_full_path,
+ const wchar_t* directory_full_path);
+
+ void Stop();
+ void CheckFinish();
+ void SaveNextFile(bool process_all_remainder_items);
+ void DoSavingProcess();
+
+ // Create a file name based on the response from the server.
+ bool GenerateFilename(const std::string& disposition,
+ const std::wstring& url,
+ bool need_html_ext,
+ std::wstring* generated_name);
+
+ // Get all savable resource links from current web page, include main
+ // frame and sub-frame.
+ void GetAllSavableResourceLinksForCurrentPage();
+ // Get html data by serializing all frames of current page with lists
+ // which contain all resource links that have local copy.
+ void GetSerializedHtmlDataForCurrentPageWithLocalLinks();
+
+ SaveItem* LookupItemInProcessBySaveId(int32 save_id);
+ void PutInProgressItemToSavedMap(SaveItem* save_item);
+
+ typedef base::hash_map<std::wstring, SaveItem*> SaveUrlItemMap;
+ // in_progress_items_ is map of all saving job in in-progress state.
+ SaveUrlItemMap in_progress_items_;
+ // saved_failed_items_ is map of all saving job which are failed.
+ SaveUrlItemMap saved_failed_items_;
+
+ // The number of in process SaveItems.
+ int in_process_count() const {
+ return static_cast<int>(in_progress_items_.size());
+ }
+
+ // The number of all SaveItems which have completed, including success items
+ // and failed items.
+ int completed_count() const {
+ return static_cast<int>(saved_success_items_.size() +
+ saved_failed_items_.size());
+ }
+
+ typedef std::queue<SaveItem*> SaveItemQueue;
+ // A queue for items we are about to start saving.
+ SaveItemQueue waiting_item_queue_;
+
+ typedef base::hash_map<int32, SaveItem*> SavedItemMap;
+ // saved_success_items_ is map of all saving job which are successfully saved.
+ SavedItemMap saved_success_items_;
+
+ // The request context which provides application-specific context for
+ // URLRequest instances.
+ scoped_refptr<URLRequestContext> request_context_;
+
+ // Non-owning pointer for handling file writing on the file thread.
+ SaveFileManager* file_manager_;
+
+ WebContents* web_contents_;
+
+ // We use a fake DownloadItem here in order to reuse the DownloadItemView.
+ // This class owns the pointer.
+ DownloadItem* download_;
+
+ // The URL of the page the user wants to save.
+ std::wstring page_url_;
+ std::wstring saved_main_file_path_;
+ std::wstring saved_main_directory_path_;
+
+ // Indicates whether the actual saving job is finishing or not.
+ bool finished_;
+
+ // Indicates whether user canceled the saving job.
+ bool user_canceled_;
+
+ // Indicates whether user get disk error.
+ bool disk_error_occurred_;
+
+ // Type about saving page as only-html or complete-html.
+ SavePackageType save_type_;
+
+ // Number of all need to be saved resources.
+ int all_save_items_count_;
+
+ typedef base::hash_set<std::wstring> FileNameSet;
+ // This set is used to eliminate duplicated file names in saving directory.
+ FileNameSet file_name_set_;
+
+ typedef base::hash_map<std::wstring, uint32> FileNameCountMap;
+ // This map is used to track serial number for specified filename.
+ FileNameCountMap file_name_count_map_;
+
+ // Indicates current waiting state when SavePackage try to get something
+ // from outside.
+ WaitState wait_state_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SavePackage);
+};
+
+#endif // CHROME_BROWSER_DOWNLOAD_SAVE_PACKAGE_H__
diff --git a/chrome/browser/download/save_package_unittest.cc b/chrome/browser/download/save_package_unittest.cc
new file mode 100644
index 0000000..1889653
--- /dev/null
+++ b/chrome/browser/download/save_package_unittest.cc
@@ -0,0 +1,170 @@
+// 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 "base/logging.h"
+#include "chrome/browser/download/save_package.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// 0 1 2 3 4 5 6
+// 0123456789012345678901234567890123456789012345678901234567890123456789
+static const wchar_t* const kLongFilePath =
+ L"C:\\EFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
+ L"89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
+ L"6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
+ L"456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a.htm";
+
+static const wchar_t* const kLongDirPath =
+ L"C:\\EFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
+ L"89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
+ L"6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
+ L"456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a_files";
+
+class SavePackageTest : public testing::Test {
+ public:
+ SavePackageTest() {
+ save_package_success_ = new SavePackage(L"c:\\testfile.htm",
+ L"c:\\testfile_files");
+ save_package_fail_ = new SavePackage(kLongFilePath, kLongDirPath);
+ }
+
+ bool GetGeneratedFilename(bool need_success_generate_filename,
+ const std::string& disposition,
+ const std::wstring& url,
+ bool need_htm_ext,
+ std::wstring* generated_name) {
+ SavePackage* save_package;
+ if (need_success_generate_filename)
+ save_package = save_package_success_.get();
+ else
+ save_package = save_package_fail_.get();
+ return save_package->GenerateFilename(disposition, url, need_htm_ext,
+ generated_name);
+ }
+
+ protected:
+ DISALLOW_EVIL_CONSTRUCTORS(SavePackageTest);
+
+ private:
+ // SavePackage for successfully generating file name.
+ scoped_refptr<SavePackage> save_package_success_;
+ // SavePackage for failed generating file name.
+ scoped_refptr<SavePackage> save_package_fail_;
+};
+
+static const struct {
+ const char* disposition;
+ const wchar_t* url;
+ const wchar_t* expected_name;
+ bool need_htm_ext;
+} kGeneratedFiles[] = {
+ // We mainly focus on testing duplicated names here, since retrieving file
+ // name from disposition and url has been tested in DownloadManagerTest.
+
+ // No useful information in disposition or URL, use default.
+ {"1.html", L"http://www.savepage.com/", L"saved_resource.htm", true},
+
+ // No duplicate occurs.
+ {"filename=1.css", L"http://www.savepage.com", L"1.css", false},
+
+ // No duplicate occurs.
+ {"filename=1.js", L"http://www.savepage.com", L"1.js", false},
+
+ // Append numbers for duplicated names.
+ {"filename=1.css", L"http://www.savepage.com", L"1(1).css", false},
+
+ // No duplicate occurs.
+ {"filename=1(1).js", L"http://www.savepage.com", L"1(1).js", false},
+
+ // Append numbers for duplicated names.
+ {"filename=1.css", L"http://www.savepage.com", L"1(2).css", false},
+
+ // Change number for duplicated names.
+ {"filename=1(1).css", L"http://www.savepage.com", L"1(3).css", false},
+
+ // No duplicate occurs.
+ {"filename=1(11).css", L"http://www.savepage.com", L"1(11).css", false},
+
+ // test length of file path is great than MAX_PATH(260 characters).
+ {"",
+ // 0 1 2 3 4 5 6
+ // 0123456789012345678901234567890123456789012345678901234567890123456789
+ // 0 1 2 3 4
+ // 01234567890123456789012345678901234567890123456789
+ L"http://www.google.com/ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmn"
+ L"opqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijkl"
+ L"mnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij"
+ L"klmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh"
+ L"test.css",
+ // 0 1 2 3 4 5 6
+ // 0123456789012345678901234567890123456789012345678901234567890123456789
+ L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
+ L"89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
+ L"6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
+ L"456789ABCDEFGHIJKLMNOPQRSTU.css",
+ false},
+
+ // test duplicated file name, its length of file path is great than
+ // MAX_PATH(260 characters).
+ {"",
+ // 0 1 2 3 4 5 6
+ // 0123456789012345678901234567890123456789012345678901234567890123456789
+ // 0 1 2 3 4
+ // 01234567890123456789012345678901234567890123456789
+ L"http://www.google.com/ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmn"
+ L"opqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijkl"
+ L"mnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij"
+ L"klmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh"
+ L"test.css",
+ // 0 1 2 3 4 5 6
+ // 0123456789012345678901234567890123456789012345678901234567890123456789
+ L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
+ L"89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
+ L"6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
+ L"456789ABCDEFGHIJKLMNO(1).css",
+ false},
+
+ {"",
+ // 0 1 2 3 4 5 6
+ // 0123456789012345678901234567890123456789012345678901234567890123456789
+ // 0 1 2 3 4
+ // 01234567890123456789012345678901234567890123456789
+ L"http://www.google.com/ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmn"
+ L"opqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijkl"
+ L"mnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij"
+ L"klmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh"
+ L"test.css",
+ // 0 1 2 3 4 5 6
+ // 0123456789012345678901234567890123456789012345678901234567890123456789
+ L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
+ L"89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
+ L"6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
+ L"456789ABCDEFGHIJKLMNO(2).css",
+ false}
+};
+
+TEST_F(SavePackageTest, TestSuccessfullyGenerateSavePackageFilename) {
+ for (int i = 0; i < arraysize(kGeneratedFiles); ++i) {
+ std::wstring file_name;
+ bool ok = GetGeneratedFilename(true,
+ kGeneratedFiles[i].disposition,
+ kGeneratedFiles[i].url,
+ kGeneratedFiles[i].need_htm_ext,
+ &file_name);
+ ASSERT_TRUE(ok);
+ EXPECT_EQ(file_name, kGeneratedFiles[i].expected_name);
+ }
+}
+
+TEST_F(SavePackageTest, TestUnSuccessfullyGenerateSavePackageFilename) {
+ for (int i = 0; i < arraysize(kGeneratedFiles); ++i) {
+ std::wstring file_name;
+ bool ok = GetGeneratedFilename(false,
+ kGeneratedFiles[i].disposition,
+ kGeneratedFiles[i].url,
+ kGeneratedFiles[i].need_htm_ext,
+ &file_name);
+ ASSERT_FALSE(ok);
+ }
+}
+
diff --git a/chrome/browser/download/save_page_model.cc b/chrome/browser/download/save_page_model.cc
new file mode 100644
index 0000000..1a78e24
--- /dev/null
+++ b/chrome/browser/download/save_page_model.cc
@@ -0,0 +1,48 @@
+// 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/save_page_model.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/download/save_package.h"
+#include "chrome/common/l10n_util.h"
+
+#include "generated_resources.h"
+
+SavePageModel::SavePageModel(SavePackage* save, DownloadItem* download)
+ : save_(save),
+ download_(download) {
+}
+
+void SavePageModel::CancelTask() {
+ save_->Cancel(true);
+}
+
+std::wstring SavePageModel::GetStatusText() {
+ int64 size = download_->received_bytes();
+ int64 total_size = download_->total_bytes();
+
+ std::wstring status_text;
+ switch (download_->state()) {
+ case DownloadItem::IN_PROGRESS:
+ status_text = l10n_util::GetStringF(IDS_SAVE_PAGE_PROGRESS,
+ FormatNumber(size),
+ FormatNumber(total_size));
+ break;
+ case DownloadItem::COMPLETE:
+ status_text = l10n_util::GetString(IDS_SAVE_PAGE_STATUS_COMPLETED);
+ break;
+ case DownloadItem::CANCELLED:
+ status_text = l10n_util::GetString(IDS_SAVE_PAGE_STATUS_CANCELLED);
+ break;
+ case DownloadItem::REMOVING:
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ return status_text;
+}
+
diff --git a/chrome/browser/download/save_page_model.h b/chrome/browser/download/save_page_model.h
new file mode 100644
index 0000000..75a4497
--- /dev/null
+++ b/chrome/browser/download/save_page_model.h
@@ -0,0 +1,38 @@
+// 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_SAVE_PAGE_MODEL_H__
+#define CHROME_BROWSER_DOWNLOAD_SAVE_PAGE_MODEL_H__
+
+#include "chrome/browser/views/download_item_view.h"
+
+class DownloadItem;
+class SavePackage;
+
+// This class is a model class for DownloadItemView. It provides cancel
+// functionality for saving page, and also the text for displaying saving
+// status.
+class SavePageModel : public DownloadItemView::BaseDownloadItemModel {
+ public:
+ SavePageModel(SavePackage* save, DownloadItem* download);
+ virtual ~SavePageModel() { }
+
+ // Cancel the page saving.
+ virtual void CancelTask();
+
+ // Get page saving status text.
+ virtual std::wstring GetStatusText();
+
+ private:
+ // Saving page management.
+ SavePackage* save_;
+
+ // A fake download item for saving page use.
+ DownloadItem* download_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SavePageModel);
+};
+
+#endif // CHROME_BROWSER_DOWNLOAD_SAVE_PAGE_MODEL_H__
+
diff --git a/chrome/browser/download/save_page_uitest.cc b/chrome/browser/download/save_page_uitest.cc
new file mode 100644
index 0000000..4e9fca6
--- /dev/null
+++ b/chrome/browser/download/save_page_uitest.cc
@@ -0,0 +1,112 @@
+// 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 "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/download/save_package.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+#include "net/url_request/url_request_unittest.h"
+
+const std::wstring kTestDir = L"save_page";
+
+class SavePageTest : public UITest {
+ protected:
+ SavePageTest() : UITest() {}
+
+ void CheckFile(const std::wstring& client_file,
+ const std::wstring& server_file,
+ bool check_equal) {
+ bool exist = false;
+ for (int i = 0; i < 20; ++i) {
+ if (file_util::PathExists(client_file)) {
+ exist = true;
+ break;
+ }
+ Sleep(kWaitForActionMaxMsec / 20);
+ }
+ EXPECT_TRUE(exist);
+
+ if (check_equal) {
+ std::wstring server_file_name;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA,
+ &server_file_name));
+ server_file_name += L"\\" + kTestDir + L"\\" + server_file;
+ ASSERT_TRUE(file_util::PathExists(server_file_name));
+
+ int64 client_file_size = 0;
+ int64 server_file_size = 0;
+ EXPECT_TRUE(file_util::GetFileSize(client_file, &client_file_size));
+ EXPECT_TRUE(file_util::GetFileSize(server_file_name, &server_file_size));
+ EXPECT_EQ(client_file_size, server_file_size);
+ EXPECT_PRED2(file_util::ContentsEqual, client_file, server_file_name);
+ }
+
+ EXPECT_TRUE(DieFileDie(client_file, false));
+ }
+
+ virtual void SetUp() {
+ UITest::SetUp();
+ EXPECT_TRUE(file_util::CreateNewTempDirectory(L"", &save_dir_));
+ save_dir_ += file_util::kPathSeparator;
+ }
+
+ std::wstring save_dir_;
+};
+
+TEST_F(SavePageTest, SaveHTMLOnly) {
+ std::wstring file_name = L"a.htm";
+ std::wstring full_file_name = save_dir_ + file_name;
+ std::wstring dir = save_dir_ + L"a_files";
+
+ GURL url = URLRequestMockHTTPJob::GetMockUrl(kTestDir + L"/" + file_name);
+ scoped_ptr<TabProxy> tab(GetActiveTab());
+ ASSERT_TRUE(tab->NavigateToURL(url));
+ WaitUntilTabCount(1);
+
+ EXPECT_TRUE(tab->SavePage(full_file_name, dir,
+ SavePackage::SAVE_AS_ONLY_HTML));
+ EXPECT_TRUE(WaitForDownloadShelfVisible(tab.get()));
+
+ CheckFile(full_file_name, file_name, true);
+ EXPECT_FALSE(file_util::PathExists(dir));
+}
+
+TEST_F(SavePageTest, SaveCompleteHTML) {
+ std::wstring file_name = L"b.htm";
+ std::wstring full_file_name = save_dir_ + file_name;
+ std::wstring dir = save_dir_ + L"b_files";
+
+ GURL url = URLRequestMockHTTPJob::GetMockUrl(kTestDir + L"/" + file_name);
+ scoped_ptr<TabProxy> tab(GetActiveTab());
+ ASSERT_TRUE(tab->NavigateToURL(url));
+ WaitUntilTabCount(1);
+
+ EXPECT_TRUE(tab->SavePage(full_file_name, dir,
+ SavePackage::SAVE_AS_COMPLETE_HTML));
+ EXPECT_TRUE(WaitForDownloadShelfVisible(tab.get()));
+
+ CheckFile(dir + L"\\1.png", L"1.png", true);
+ CheckFile(dir + L"\\1.css", L"1.css", true);
+ CheckFile(full_file_name, file_name, false);
+ EXPECT_TRUE(DieFileDie(dir, true));
+}
+
+TEST_F(SavePageTest, NoSave) {
+ std::wstring file_name = L"c.htm";
+ std::wstring full_file_name = save_dir_ + file_name;
+ std::wstring dir = save_dir_ + L"c_files";
+
+ scoped_ptr<TabProxy> tab(GetActiveTab());
+ ASSERT_TRUE(tab->NavigateToURL(GURL(L"about:blank")));
+ WaitUntilTabCount(1);
+
+ EXPECT_FALSE(tab->SavePage(full_file_name, dir,
+ SavePackage::SAVE_AS_ONLY_HTML));
+ EXPECT_FALSE(WaitForDownloadShelfVisible(tab.get()));
+}
+
diff --git a/chrome/browser/download/save_types.h b/chrome/browser/download/save_types.h
new file mode 100644
index 0000000..1a8832c
--- /dev/null
+++ b/chrome/browser/download/save_types.h
@@ -0,0 +1,71 @@
+// 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_SAVE_TYPES_H__
+#define CHROME_BROWSER_DOWNLOAD_SAVE_TYPES_H__
+
+#include <vector>
+#include <utility>
+
+#include "base/basictypes.h"
+
+typedef std::vector<std::pair<int, std::wstring> > FinalNameList;
+typedef std::vector<int> SaveIDList;
+
+// This structure is used to handle and deliver some info
+// when processing each save item job.
+struct SaveFileCreateInfo {
+ enum SaveFileSource {
+ // This type indicates the save item that need to be retrieved
+ // from the network.
+ SAVE_FILE_FROM_NET = 0,
+ // This type indicates the save item that need to be retrieved
+ // from serializing DOM.
+ SAVE_FILE_FROM_DOM,
+ // This type indicates the save item that need to be retrieved
+ // through local file system.
+ SAVE_FILE_FROM_FILE
+ };
+
+ SaveFileCreateInfo(const std::wstring& path,
+ const std::wstring& url,
+ SaveFileSource save_source,
+ int32 save_id)
+ : path(path),
+ url(url),
+ save_id(save_id),
+ save_source(save_source),
+ render_process_id(-1),
+ render_view_id(-1),
+ request_id(-1),
+ total_bytes(0) {
+ }
+
+ SaveFileCreateInfo() : save_id(-1) {}
+
+ // SaveItem fields.
+ // The local file path of saved file.
+ std::wstring path;
+ // Original URL of the saved resource.
+ std::wstring url;
+ // Final URL of the saved resource since some URL might be redirected.
+ std::wstring final_url;
+ // The unique identifier for saving job, assigned at creation by
+ // the SaveFileManager for its internal record keeping.
+ int save_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;
+ // Disposition info from HTTP response.
+ std::string content_disposition;
+ // Total bytes of saved file.
+ int64 total_bytes;
+ // Source type of saved file.
+ SaveFileSource save_source;
+};
+
+#endif // CHROME_BROWSER_DOWNLOAD_SAVE_TYPES_H__
+