diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/save_package.cc | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/save_package.cc')
-rw-r--r-- | chrome/browser/save_package.cc | 1088 |
1 files changed, 1088 insertions, 0 deletions
diff --git a/chrome/browser/save_package.cc b/chrome/browser/save_package.cc new file mode 100644 index 0000000..ef110b7 --- /dev/null +++ b/chrome/browser/save_package.cc @@ -0,0 +1,1088 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/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_manager.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/save_file.h" +#include "chrome/browser/save_file_manager.h" +#include "chrome/browser/save_page_model.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_util::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"%s(%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, ¶m->saved_main_file_path)) + return false; + } else { + if (!win_util::SaveFileAs(container_hwnd, suggest_name, + ¶m->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(¶m->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; + } +} |