Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Side by Side Diff: chrome/browser/save_package.cc

Issue 3040: Move the Save Page code. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 12 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/save_package.h ('k') | chrome/browser/save_package_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/save_package.h"
6
7 #include "base/file_util.h"
8 #include "base/logging.h"
9 #include "base/message_loop.h"
10 #include "base/path_service.h"
11 #include "base/string_util.h"
12 #include "base/task.h"
13 #include "base/thread.h"
14 #include "base/win_util.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/download/download_manager.h"
17 #include "chrome/browser/profile.h"
18 #include "chrome/browser/render_process_host.h"
19 #include "chrome/browser/render_view_host.h"
20 #include "chrome/browser/render_view_host_delegate.h"
21 #include "chrome/browser/resource_dispatcher_host.h"
22 #include "chrome/browser/save_file.h"
23 #include "chrome/browser/save_file_manager.h"
24 #include "chrome/browser/save_page_model.h"
25 #include "chrome/browser/tab_util.h"
26 #include "chrome/browser/web_contents.h"
27 #include "chrome/browser/views/download_shelf_view.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/common/l10n_util.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/common/pref_service.h"
32 #include "chrome/common/stl_util-inl.h"
33 #include "chrome/common/win_util.h"
34 #include "net/base/net_util.h"
35 #include "net/url_request/url_request_context.h"
36 #include "webkit/glue/dom_serializer_delegate.h"
37
38 #include "generated_resources.h"
39
40 // Default name which will be used when we can not get proper name from
41 // resource URL.
42 static const wchar_t kDefaultSaveName[] = L"saved_resource";
43
44 // Maximum number of file ordinal number. I think it's big enough for resolving
45 // name-conflict files which has same base file name.
46 static const int32 kMaxFileOrdinalNumber = 9999;
47
48 // Maximum length for file path. Since Windows have MAX_PATH limitation for
49 // file path, we need to make sure length of file path of every saved file
50 // is less than MAX_PATH
51 static const uint32 kMaxFilePathLength = MAX_PATH - 1;
52
53 // Maximum length for file ordinal number part. Since we only support the
54 // maximum 9999 for ordinal number, which means maximum file ordinal number part
55 // should be "(9998)", so the value is 6.
56 static const uint32 kMaxFileOrdinalNumberPartLength = 6;
57
58 SavePackage::SavePackage(WebContents* web_content,
59 SavePackageType save_type,
60 const std::wstring& file_full_path,
61 const std::wstring& directory_full_path)
62 : web_contents_(web_content),
63 save_type_(save_type),
64 saved_main_file_path_(file_full_path),
65 saved_main_directory_path_(directory_full_path),
66 all_save_items_count_(0),
67 disk_error_occurred_(false),
68 user_canceled_(false),
69 download_(NULL),
70 finished_(false),
71 wait_state_(INITIALIZE) {
72 DCHECK(web_content);
73 const GURL& current_page_url = web_contents_->GetURL();
74 DCHECK(current_page_url.is_valid());
75 page_url_ = UTF8ToWide(current_page_url.spec());
76 DCHECK(save_type_ == SAVE_AS_ONLY_HTML ||
77 save_type_ == SAVE_AS_COMPLETE_HTML);
78 DCHECK(!saved_main_file_path_.empty() &&
79 saved_main_file_path_.length() <= kMaxFilePathLength);
80 DCHECK(!saved_main_directory_path_.empty() &&
81 saved_main_directory_path_.length() < kMaxFilePathLength);
82 }
83
84 // This is for testing use. Set |finished_| as true because we don't want
85 // method Cancel to be be called in destructor in test mode.
86 SavePackage::SavePackage(const wchar_t* file_full_path,
87 const wchar_t* directory_full_path)
88 : all_save_items_count_(0),
89 saved_main_file_path_(file_full_path),
90 saved_main_directory_path_(directory_full_path),
91 finished_(true),
92 download_(NULL),
93 user_canceled_(false),
94 disk_error_occurred_(false) {
95 DCHECK(!saved_main_file_path_.empty() &&
96 saved_main_file_path_.length() <= kMaxFilePathLength);
97 DCHECK(!saved_main_directory_path_.empty() &&
98 saved_main_directory_path_.length() < kMaxFilePathLength);
99 }
100
101 SavePackage::~SavePackage() {
102 // Stop receiving saving job's updates
103 if (!finished_ && !canceled()) {
104 // Unexpected quit.
105 Cancel(true);
106 }
107
108 DCHECK(all_save_items_count_ == (waiting_item_queue_.size() +
109 completed_count() +
110 in_process_count()));
111 // Free all SaveItems.
112 while (!waiting_item_queue_.empty()) {
113 // We still have some items which are waiting for start to save.
114 SaveItem* save_item = waiting_item_queue_.front();
115 waiting_item_queue_.pop();
116 delete save_item;
117 }
118
119 STLDeleteValues(&saved_success_items_);
120 STLDeleteValues(&in_progress_items_);
121 STLDeleteValues(&saved_failed_items_);
122
123 if (download_) {
124 // We call this to remove the view from the shelf. It will invoke
125 // DownloadManager::RemoveDownload, but since the fake DownloadItem is not
126 // owned by DownloadManager, it will do nothing to our fake item.
127 download_->Remove();
128 delete download_;
129 download_ = NULL;
130 }
131 file_manager_ = NULL;
132 }
133
134 // Cancel all in progress request, might be called by user or internal error.
135 void SavePackage::Cancel(bool user_action) {
136 if (!canceled()) {
137 if (user_action)
138 user_canceled_ = true;
139 else
140 disk_error_occurred_ = true;
141 Stop();
142 }
143 }
144
145 // Initialize the SavePackage.
146 bool SavePackage::Init() {
147 // Set proper running state.
148 if (wait_state_ != INITIALIZE)
149 return false;
150
151 wait_state_ = START_PROCESS;
152
153 // Initialize the request context and resource dispatcher.
154 Profile* profile = web_contents_->profile();
155 if (!profile) {
156 NOTREACHED();
157 return false;
158 }
159
160 request_context_ = profile->GetRequestContext();
161
162 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
163 if (!rdh) {
164 NOTREACHED();
165 return false;
166 }
167
168 file_manager_ = rdh->save_file_manager();
169 if (!file_manager_) {
170 NOTREACHED();
171 return false;
172 }
173
174 // Create the fake DownloadItem and display the view.
175 download_ = new DownloadItem(1, saved_main_file_path_,
176 page_url_, Time::Now(), 0, -1, -1);
177 download_->set_manager(web_contents_->profile()->GetDownloadManager());
178 DownloadShelfView* shelf = web_contents_->GetDownloadShelfView();
179 shelf->AddDownloadView(new DownloadItemView(
180 download_, shelf, new SavePageModel(this, download_)));
181 web_contents_->SetDownloadShelfVisible(true);
182
183 // Check save type and process the save page job.
184 if (save_type_ == SAVE_AS_COMPLETE_HTML) {
185 // Get directory
186 DCHECK(!saved_main_directory_path_.empty());
187 GetAllSavableResourceLinksForCurrentPage();
188 } else {
189 wait_state_ = NET_FILES;
190 GURL u(page_url_);
191 SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
192 SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
193 SaveFileCreateInfo::SAVE_FILE_FROM_NET;
194 SaveItem* save_item = new SaveItem(page_url_,
195 L"",
196 this,
197 save_source);
198 // Add this item to waiting list.
199 waiting_item_queue_.push(save_item);
200 all_save_items_count_ = 1;
201 download_->set_total_bytes(1);
202
203 DoSavingProcess();
204 }
205
206 return true;
207 }
208
209 // Generate name for saving resource.
210 bool SavePackage::GenerateFilename(const std::string& disposition,
211 const std::wstring& url,
212 bool need_html_ext,
213 std::wstring* generated_name) {
214 std::wstring file_name =
215 net::GetSuggestedFilename(GURL(url), disposition, kDefaultSaveName);
216
217 DCHECK(!file_name.empty());
218 // Check whether we have same name before.
219 std::wstring::size_type last_dot = file_name.rfind(L'.');
220 std::wstring pure_file_name, file_name_ext;
221 if (last_dot == std::wstring::npos) {
222 pure_file_name = file_name;
223 } else {
224 pure_file_name = std::wstring(file_name, 0, last_dot);
225 file_name_ext = std::wstring(file_name, last_dot);
226 }
227 // If it is HTML resource, use ".htm" as its extension name.
228 if (need_html_ext)
229 file_name_ext = L".htm";
230 if (file_name_ext == L".")
231 file_name_ext.clear();
232
233 // Get safe pure file name.
234 if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
235 kMaxFilePathLength, &pure_file_name))
236 return false;
237
238 file_name = pure_file_name + file_name_ext;
239
240 // Check whether we already have same name.
241 if (file_name_set_.find(file_name) == file_name_set_.end()) {
242 file_name_set_.insert(file_name);
243 } else {
244 // Found same name, increase the ordinal number for the file name.
245 std::wstring base_file_name, file_ordinal_number;
246
247 if (!GetBaseFileNameAndFileOrdinalNumber(pure_file_name, &base_file_name,
248 &file_ordinal_number))
249 base_file_name = pure_file_name;
250
251 // We need to make sure the length of base file name plus maximum ordinal
252 // number path will be less than or equal to kMaxFilePathLength.
253 if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
254 kMaxFilePathLength - kMaxFileOrdinalNumberPartLength, &base_file_name))
255 return false;
256
257 // Prepare the new ordinal number.
258 uint32 ordinal_number;
259 FileNameCountMap::iterator it = file_name_count_map_.find(base_file_name);
260 if (it == file_name_count_map_.end()) {
261 // First base-name-conflict resolving, use 1 as initial ordinal number.
262 file_name_count_map_[base_file_name] = 1;
263 ordinal_number = 1;
264 } else {
265 // We have met same base-name conflict, use latest ordinal number.
266 ordinal_number = it->second;
267 }
268
269 if (ordinal_number > (kMaxFileOrdinalNumber - 1)) {
270 // Use a random file from temporary file.
271 file_util::CreateTemporaryFileName(&file_name);
272 file_name = file_util::GetFilenameFromPath(file_name);
273 // Get safe pure file name.
274 if (!GetSafePureFileName(saved_main_directory_path_, std::wstring(),
275 kMaxFilePathLength, &file_name))
276 return false;
277 } else {
278 uint32 i;
279 for (i = ordinal_number; i < kMaxFileOrdinalNumber; ++i) {
280 std::wstring new_name =
281 StringPrintf(L"%ls(%d)", base_file_name.c_str(), i) + file_name_ext;
282 if (file_name_set_.find(new_name) == file_name_set_.end()) {
283 // Resolved name conflict.
284 file_name = new_name;
285 file_name_count_map_[base_file_name] = ++i;
286 break;
287 }
288 }
289 }
290
291 file_name_set_.insert(file_name);
292 }
293
294 DCHECK(!file_name.empty());
295 generated_name->assign(file_name);
296
297 return true;
298 }
299
300 // We have received a message from SaveFileManager about a new saving job. We
301 // create a SaveItem and store it in our in_progress list.
302 void SavePackage::StartSave(const SaveFileCreateInfo* info) {
303 DCHECK(info && !info->url.empty());
304
305 SaveUrlItemMap::iterator it = in_progress_items_.find(info->url);
306 if (it == in_progress_items_.end()) {
307 // If not found, we must have cancel action.
308 DCHECK(canceled());
309 return;
310 }
311 SaveItem* save_item = it->second;
312
313 DCHECK(!saved_main_file_path_.empty());
314
315 save_item->SetSaveId(info->save_id);
316 save_item->SetTotalBytes(info->total_bytes);
317
318 // Determine the proper path for a saving job, by choosing either the default
319 // save directory, or prompting the user.
320 DCHECK(!save_item->has_final_name());
321 if (info->url != page_url_) {
322 std::wstring generated_name;
323 // For HTML resource file, make sure it will have .htm as extension name,
324 // otherwise, when you open the saved page in Chrome again, download
325 // file manager will treat it as downloadable resource, and download it
326 // instead of opening it as HTML.
327 bool need_html_ext =
328 info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM;
329 if (!GenerateFilename(info->content_disposition,
330 info->url,
331 need_html_ext,
332 &generated_name)) {
333 // We can not generate file name for this SaveItem, so we cancel the
334 // saving page job if the save source is from serialized DOM data.
335 // Otherwise, it means this SaveItem is sub-resource type, we treat it
336 // as an error happened on saving. We can ignore this type error for
337 // sub-resource links which will be resolved as absolute links instead
338 // of local links in final saved contents.
339 if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM)
340 Cancel(true);
341 else
342 SaveFinished(save_item->save_id(), 0, false);
343 return;
344 }
345
346 // When saving page as only-HTML, we only have a SaveItem whose url
347 // must be page_url_.
348 DCHECK(save_type_ == SAVE_AS_COMPLETE_HTML);
349 DCHECK(!saved_main_directory_path_.empty());
350
351 // Now we get final name retrieved from GenerateFilename, we will use it
352 // rename the SaveItem.
353 std::wstring final_name = saved_main_directory_path_;
354 file_util::AppendToPath(&final_name, generated_name);
355 save_item->Rename(final_name);
356 } else {
357 // It is the main HTML file, use the name chosen by the user.
358 save_item->Rename(saved_main_file_path_);
359 }
360
361 // If the save source is from file system, inform SaveFileManager to copy
362 // corresponding file to the file path which this SaveItem specifies.
363 if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) {
364 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
365 NewRunnableMethod(file_manager_,
366 &SaveFileManager::SaveLocalFile,
367 save_item->url(),
368 save_item->save_id(),
369 GetTabId()));
370 return;
371 }
372
373 // Check whether we begin to require serialized HTML data.
374 if (save_type_ == SAVE_AS_COMPLETE_HTML && wait_state_ == HTML_DATA) {
375 // Inform backend to serialize the all frames' DOM and send serialized
376 // HTML data back.
377 GetSerializedHtmlDataForCurrentPageWithLocalLinks();
378 }
379 }
380
381 // Look up SaveItem by save id from in progress map.
382 SaveItem* SavePackage::LookupItemInProcessBySaveId(int32 save_id) {
383 if (in_process_count()) {
384 SaveUrlItemMap::iterator it = in_progress_items_.begin();
385 for (; it != in_progress_items_.end(); ++it) {
386 SaveItem* save_item = it->second;
387 DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
388 if (save_item->save_id() == save_id)
389 return save_item;
390 }
391 }
392 return NULL;
393 }
394
395 // Remove SaveItem from in progress map and put it to saved map.
396 void SavePackage::PutInProgressItemToSavedMap(SaveItem* save_item) {
397 SaveUrlItemMap::iterator it = in_progress_items_.find(
398 save_item->url());
399 DCHECK(it != in_progress_items_.end());
400 DCHECK(save_item == it->second);
401 in_progress_items_.erase(it);
402
403 if (save_item->success()) {
404 // Add it to saved_success_items_.
405 DCHECK(saved_success_items_.find(save_item->save_id()) ==
406 saved_success_items_.end());
407 saved_success_items_[save_item->save_id()] = save_item;
408 } else {
409 // Add it to saved_failed_items_.
410 DCHECK(saved_failed_items_.find(save_item->url()) ==
411 saved_failed_items_.end());
412 saved_failed_items_[save_item->url()] = save_item;
413 }
414 }
415
416 // Called for updating saving state.
417 bool SavePackage::UpdateSaveProgress(int32 save_id,
418 int64 size,
419 bool write_success) {
420 // Because we might have canceled this saving job before,
421 // so we might not find corresponding SaveItem.
422 SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
423 if (!save_item)
424 return false;
425
426 save_item->Update(size);
427
428 // If we got disk error, cancel whole save page job.
429 if (!write_success) {
430 // Cancel job with reason of disk error.
431 Cancel(false);
432 }
433 return true;
434 }
435
436 // Stop all page saving jobs that are in progress and instruct the file thread
437 // to delete all saved files.
438 void SavePackage::Stop() {
439 // When stopping, if it still has some items in in_progress, cancel them.
440 DCHECK(canceled());
441 if (in_process_count()) {
442 SaveUrlItemMap::iterator it = in_progress_items_.begin();
443 for (; it != in_progress_items_.end(); ++it) {
444 SaveItem* save_item = it->second;
445 DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
446 save_item->Cancel();
447 }
448 // Remove all in progress item to saved map. For failed items, they will
449 // be put into saved_failed_items_, for successful item, they will be put
450 // into saved_success_items_.
451 while (in_process_count())
452 PutInProgressItemToSavedMap(in_progress_items_.begin()->second);
453 }
454
455 // This vector contains the save ids of the save files which SaveFileManager
456 // needs to remove from its save_file_map_.
457 SaveIDList save_ids;
458 for (SavedItemMap::iterator it = saved_success_items_.begin();
459 it != saved_success_items_.end(); ++it)
460 save_ids.push_back(it->first);
461 for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
462 it != saved_failed_items_.end(); ++it)
463 save_ids.push_back(it->second->save_id());
464
465 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
466 NewRunnableMethod(file_manager_,
467 &SaveFileManager::RemoveSavedFileFromFileMap,
468 save_ids));
469
470 finished_ = true;
471 wait_state_ = FAILED;
472
473 // Inform the DownloadItem we have canceled whole save page job.
474 DCHECK(download_);
475 download_->Cancel(false);
476 }
477
478 void SavePackage::CheckFinish() {
479 if (in_process_count() || finished_)
480 return;
481
482 std::wstring dir = save_type_ == SAVE_AS_COMPLETE_HTML ?
483 saved_main_directory_path_ :
484 L"";
485
486 // This vector contains the final names of all the successfully saved files
487 // along with their save ids. It will be passed to SaveFileManager to do the
488 // renaming job.
489 FinalNameList final_names;
490 for (SavedItemMap::iterator it = saved_success_items_.begin();
491 it != saved_success_items_.end(); ++it)
492 final_names.push_back(std::make_pair(it->first,
493 it->second->full_path()));
494
495 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
496 NewRunnableMethod(file_manager_,
497 &SaveFileManager::RenameAllFiles,
498 final_names,
499 dir,
500 web_contents_->process()->host_id(),
501 web_contents_->render_view_host()->routing_id()));
502 }
503
504 // Successfully finished all items of this SavePackage.
505 void SavePackage::Finish() {
506 // User may cancel the job when we're moving files to the final directory.
507 if (canceled())
508 return;
509
510 wait_state_ = SUCCESSFUL;
511 finished_ = true;
512
513 // This vector contains the save ids of the save files which SaveFileManager
514 // needs to remove from its save_file_map_.
515 SaveIDList save_ids;
516 for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
517 it != saved_failed_items_.end(); ++it)
518 save_ids.push_back(it->second->save_id());
519
520 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
521 NewRunnableMethod(file_manager_,
522 &SaveFileManager::RemoveSavedFileFromFileMap,
523 save_ids));
524
525 DCHECK(download_);
526 download_->Finished(all_save_items_count_);
527 }
528
529 // Called for updating end state.
530 void SavePackage::SaveFinished(int32 save_id, int64 size, bool is_success) {
531 // Because we might have canceled this saving job before,
532 // so we might not find corresponding SaveItem. Just ignore it.
533 SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
534 if (!save_item)
535 return;
536
537 // Let SaveItem set end state.
538 save_item->Finish(size, is_success);
539 // Remove the associated save id and SavePackage.
540 file_manager_->RemoveSaveFile(save_id, save_item->url(), this);
541
542 PutInProgressItemToSavedMap(save_item);
543
544 // Inform the DownloadItem to update UI.
545 DCHECK(download_);
546 // We use the received bytes as number of saved files.
547 download_->Update(completed_count());
548
549 if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM &&
550 save_item->url() == page_url_ && !save_item->received_bytes()) {
551 // If size of main HTML page is 0, treat it as disk error.
552 Cancel(false);
553 return;
554 }
555
556 if (canceled()) {
557 DCHECK(finished_);
558 return;
559 }
560
561 // Continue processing the save page job.
562 DoSavingProcess();
563
564 // Check whether we can successfully finish whole job.
565 CheckFinish();
566 }
567
568 // Sometimes, the net io will only call SaveFileManager::SaveFinished with
569 // save id -1 when it encounters error. Since in this case, save id will be
570 // -1, so we can only use URL to find which SaveItem is associated with
571 // this error.
572 // Saving an item failed. If it's a sub-resource, ignore it. If the error comes
573 // from serializing HTML data, then cancel saving page.
574 void SavePackage::SaveFailed(const std::wstring& save_url) {
575 SaveUrlItemMap::iterator it = in_progress_items_.find(save_url);
576 if (it == in_progress_items_.end()) {
577 NOTREACHED(); // Should not exist!
578 return;
579 }
580 SaveItem* save_item = it->second;
581
582 save_item->Finish(0, false);
583
584 PutInProgressItemToSavedMap(save_item);
585
586 // Inform the DownloadItem to update UI.
587 DCHECK(download_);
588 // We use the received bytes as number of saved files.
589 download_->Update(completed_count());
590
591 if (save_type_ == SAVE_AS_ONLY_HTML ||
592 save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
593 // We got error when saving page. Treat it as disk error.
594 Cancel(true);
595 }
596
597 if (canceled()) {
598 DCHECK(finished_);
599 return;
600 }
601
602 // Continue processing the save page job.
603 DoSavingProcess();
604
605 CheckFinish();
606 }
607
608 void SavePackage::SaveCanceled(SaveItem* save_item) {
609 // Call the RemoveSaveFile in UI thread.
610 file_manager_->RemoveSaveFile(save_item->save_id(),
611 save_item->url(),
612 this);
613 if (save_item->save_id() != -1)
614 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
615 NewRunnableMethod(file_manager_,
616 &SaveFileManager::CancelSave,
617 save_item->save_id()));
618 }
619
620 // Initiate a saving job of a specific URL. We send the request to
621 // SaveFileManager, which will dispatch it to different approach according to
622 // the save source. Parameter process_all_remaining_items indicates whether
623 // we need to save all remaining items.
624 void SavePackage::SaveNextFile(bool process_all_remaining_items) {
625 DCHECK(web_contents_);
626 DCHECK(waiting_item_queue_.size());
627
628 do {
629 // Pop SaveItem from waiting list.
630 SaveItem* save_item = waiting_item_queue_.front();
631 waiting_item_queue_.pop();
632
633 // Add the item to in_progress_items_.
634 SaveUrlItemMap::iterator it = in_progress_items_.find(
635 save_item->url());
636 DCHECK(it == in_progress_items_.end());
637 in_progress_items_[save_item->url()] = save_item;
638 save_item->Start();
639 file_manager_->SaveURL(save_item->url(),
640 save_item->referrer(),
641 web_contents_->process()->host_id(),
642 web_contents_->render_view_host()->routing_id(),
643 save_item->save_source(),
644 save_item->full_path(),
645 request_context_.get(),
646 this);
647 } while (process_all_remaining_items && waiting_item_queue_.size());
648 }
649
650
651 // Open download page in windows explorer on file thread, to avoid blocking the
652 // user interface.
653 void SavePackage::ShowDownloadInShell() {
654 DCHECK(file_manager_);
655 DCHECK(finished_ && !canceled() && !saved_main_file_path_.empty());
656 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
657 NewRunnableMethod(file_manager_,
658 &SaveFileManager::OnShowSavedFileInShell,
659 saved_main_file_path_));
660 }
661
662 // Calculate the percentage of whole save page job.
663 int SavePackage::PercentComplete() {
664 if (!all_save_items_count_)
665 return 0;
666 else if (!in_process_count())
667 return 100;
668 else
669 return completed_count() / all_save_items_count_;
670 }
671
672 // Continue processing the save page job after one SaveItem has been
673 // finished.
674 void SavePackage::DoSavingProcess() {
675 if (save_type_ == SAVE_AS_COMPLETE_HTML) {
676 // We guarantee that images and JavaScripts must be downloaded first.
677 // So when finishing all those sub-resources, we will know which
678 // sub-resource's link can be replaced with local file path, which
679 // sub-resource's link need to be replaced with absolute URL which
680 // point to its internet address because it got error when saving its data.
681 SaveItem* save_item = NULL;
682 // Start a new SaveItem job if we still have job in waiting queue.
683 if (waiting_item_queue_.size()) {
684 DCHECK(wait_state_ == NET_FILES);
685 save_item = waiting_item_queue_.front();
686 if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
687 SaveNextFile(false);
688 } else if (!in_process_count()) {
689 // If there is no in-process SaveItem, it means all sub-resources
690 // have been processed. Now we need to start serializing HTML DOM
691 // for the current page to get the generated HTML data.
692 wait_state_ = HTML_DATA;
693 // All non-HTML resources have been finished, start all remaining
694 // HTML files.
695 SaveNextFile(true);
696 }
697 } else if (in_process_count()) {
698 // Continue asking for HTML data.
699 DCHECK(wait_state_ == HTML_DATA);
700 }
701 } else {
702 // Save as HTML only.
703 DCHECK(wait_state_ == NET_FILES);
704 DCHECK(save_type_ == SAVE_AS_ONLY_HTML);
705 if (waiting_item_queue_.size()) {
706 DCHECK(all_save_items_count_ == waiting_item_queue_.size());
707 SaveNextFile(false);
708 }
709 }
710 }
711
712 int SavePackage::GetTabId() {
713 DCHECK(web_contents_);
714 return web_contents_->process()->host_id();
715 }
716
717 // After finishing all SaveItems which need to get data from net.
718 // We collect all URLs which have local storage and send the
719 // map:(originalURL:currentLocalPath) to render process (backend).
720 // Then render process will serialize DOM and send data to us.
721 void SavePackage::GetSerializedHtmlDataForCurrentPageWithLocalLinks() {
722 if (wait_state_ != HTML_DATA)
723 return;
724 std::vector<std::wstring> saved_links;
725 std::vector<std::wstring> saved_file_paths;
726 int successful_started_items_count = 0;
727
728 // Collect all saved items which have local storage.
729 // First collect the status of all the resource files and check whether they
730 // have created local files although they have not been completely saved.
731 // If yes, the file can be saved. Otherwise, there is a disk error, so we
732 // need to cancel the page saving job.
733 for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
734 it != in_progress_items_.end(); ++it) {
735 DCHECK(it->second->save_source() ==
736 SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
737 if (it->second->has_final_name())
738 successful_started_items_count++;
739 saved_links.push_back(it->second->url());
740 saved_file_paths.push_back(it->second->file_name());
741 }
742
743 // If not all file of HTML resource have been started, then wait.
744 if (successful_started_items_count != in_process_count())
745 return;
746
747 // Collect all saved success items.
748 for (SavedItemMap::iterator it = saved_success_items_.begin();
749 it != saved_success_items_.end(); ++it) {
750 DCHECK(it->second->has_final_name());
751 saved_links.push_back(it->second->url());
752 saved_file_paths.push_back(it->second->file_name());
753 }
754
755 // Get the relative directory name.
756 std::wstring::size_type last_slash = saved_main_directory_path_.rfind(L'\\');
757 DCHECK(last_slash != std::wstring::npos);
758 std::wstring relative_dir_name = std::wstring(saved_main_directory_path_,
759 last_slash + 1);
760
761 relative_dir_name = std::wstring(L"./") + relative_dir_name + L"/";
762
763 web_contents_->GetSerializedHtmlDataForCurrentPageWithLocalLinks(
764 saved_links, saved_file_paths, relative_dir_name);
765 }
766
767 // Process the serialized HTML content data of a specified web page
768 // retrieved from render process.
769 void SavePackage::ProcessSerializedHtmlData(const GURL& frame_url,
770 const std::string& data,
771 int32 status) {
772 webkit_glue::DomSerializerDelegate::PageSavingSerializationStatus flag =
773 static_cast<webkit_glue::DomSerializerDelegate::PageSavingSerializationSta tus>
774 (status);
775 // Check current state.
776 if (wait_state_ != HTML_DATA)
777 return;
778
779 int tab_id = GetTabId();
780 // If the all frames are finished saving, we need to close the
781 // remaining SaveItems.
782 if (flag == webkit_glue::DomSerializerDelegate::ALL_FRAMES_ARE_FINISHED) {
783 for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
784 it != in_progress_items_.end(); ++it) {
785 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
786 NewRunnableMethod(file_manager_,
787 &SaveFileManager::SaveFinished,
788 it->second->save_id(),
789 it->second->url(),
790 tab_id,
791 true));
792 }
793 return;
794 }
795
796 std::wstring current_frame_url = UTF8ToWide(frame_url.spec());
797 SaveUrlItemMap::iterator it = in_progress_items_.find(current_frame_url);
798 if (it == in_progress_items_.end())
799 return;
800 SaveItem* save_item = it->second;
801 DCHECK(save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
802
803 if (!data.empty()) {
804 // Prepare buffer for saving HTML data.
805 char* new_data = static_cast<char*>(new char[data.size()]);
806 memcpy(new_data, data.data(), data.size());
807
808 // Call write file functionality in file thread.
809 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
810 NewRunnableMethod(file_manager_,
811 &SaveFileManager::UpdateSaveProgress,
812 save_item->save_id(),
813 new_data,
814 static_cast<int>(data.size())));
815 }
816
817 // Current frame is completed saving, call finish in file thread.
818 if (flag == webkit_glue::DomSerializerDelegate::CURRENT_FRAME_IS_FINISHED) {
819 file_manager_->GetSaveLoop()->PostTask(FROM_HERE,
820 NewRunnableMethod(file_manager_,
821 &SaveFileManager::SaveFinished,
822 save_item->save_id(),
823 save_item->url(),
824 tab_id,
825 true));
826 }
827 }
828
829 // Ask for all savable resource links from backend, include main frame and
830 // sub-frame.
831 void SavePackage::GetAllSavableResourceLinksForCurrentPage() {
832 if (wait_state_ != START_PROCESS)
833 return;
834
835 wait_state_ = RESOURCES_LIST;
836 GURL main_page_url(page_url_);
837 web_contents_->GetAllSavableResourceLinksForCurrentPage(main_page_url);
838 }
839
840 // Give backend the lists which contain all resource links that have local
841 // storage, after which, render process will serialize DOM for generating
842 // HTML data.
843 void SavePackage::ProcessCurrentPageAllSavableResourceLinks(
844 const std::vector<GURL>& resources_list,
845 const std::vector<GURL>& referrers_list,
846 const std::vector<GURL>& frames_list) {
847 if (wait_state_ != RESOURCES_LIST)
848 return;
849
850 DCHECK(resources_list.size() == referrers_list.size());
851 all_save_items_count_ = static_cast<int>(resources_list.size()) +
852 static_cast<int>(frames_list.size());
853
854 // We use total bytes as the total number of files we want to save.
855 download_->set_total_bytes(all_save_items_count_);
856
857 if (all_save_items_count_) {
858 // Put all sub-resources to wait list.
859 for (int i = 0; i < static_cast<int>(resources_list.size()); ++i) {
860 const GURL& u = resources_list[i];
861 DCHECK(u.is_valid());
862 SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
863 SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
864 SaveFileCreateInfo::SAVE_FILE_FROM_NET;
865 SaveItem* save_item = new SaveItem(UTF8ToWide(u.spec()),
866 UTF8ToWide(referrers_list[i].spec()), this, save_source);
867 waiting_item_queue_.push(save_item);
868 }
869 // Put all HTML resources to wait list.
870 for (int i = 0; i < static_cast<int>(frames_list.size()); ++i) {
871 const GURL& u = frames_list[i];
872 DCHECK(u.is_valid());
873 SaveItem* save_item = new SaveItem(UTF8ToWide(u.spec()), L"",
874 this, SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
875 waiting_item_queue_.push(save_item);
876 }
877 wait_state_ = NET_FILES;
878 DoSavingProcess();
879 } else {
880 // No resource files need to be saved, treat it as user cancel.
881 Cancel(true);
882 }
883 }
884
885 std::wstring SavePackage::GetSuggestNameForSaveAs(PrefService* prefs,
886 const std::wstring& name) {
887 // Check whether the preference has the preferred directory for saving file.
888 // If not, initialize it with default directory.
889 if (!prefs->IsPrefRegistered(prefs::kSaveFileDefaultDirectory)) {
890 std::wstring default_save_path;
891 PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_save_path);
892 file_util::AppendToPath(&default_save_path,
893 l10n_util::GetString(IDS_DOWNLOAD_DIRECTORY));
894 prefs->RegisterStringPref(prefs::kSaveFileDefaultDirectory,
895 default_save_path);
896 }
897
898 // Get the directory from preference.
899 StringPrefMember save_file_path;
900 save_file_path.Init(prefs::kSaveFileDefaultDirectory, prefs, NULL);
901 DCHECK(!(*save_file_path).empty());
902
903 // Ask user for getting final saving name.
904 std::wstring suggest_name, file_name;
905
906 file_name = name;
907 file_util::ReplaceIllegalCharacters(&file_name, L' ');
908 suggest_name = *save_file_path;
909 file_util::AppendToPath(&suggest_name, file_name);
910
911 return suggest_name;
912 }
913
914 // Static.
915 bool SavePackage::GetSaveInfo(const std::wstring& suggest_name,
916 HWND container_hwnd,
917 SavePackageParam* param) {
918 // Use "Web Page, Complete" option as default choice of saving page.
919 unsigned index = 2;
920
921 // If the conetnts can not be saved as complete-HTML, do not show the
922 // file filters.
923 if (CanSaveAsComplete(param->current_tab_mime_type)) {
924 // Create filter string.
925 std::wstring filter = l10n_util::GetString(IDS_SAVE_PAGE_FILTER);
926 filter.resize(filter.size() + 2);
927 filter[filter.size() - 1] = L'\0';
928 filter[filter.size() - 2] = L'\0';
929
930 if (!win_util::SaveFileAsWithFilter(container_hwnd,
931 suggest_name, filter.c_str(), L"htm",
932 &index, &param->saved_main_file_path))
933 return false;
934 } else {
935 if (!win_util::SaveFileAs(container_hwnd, suggest_name,
936 &param->saved_main_file_path))
937 return false;
938 // Set save-as type to only-HTML if the contents of current tab can not be
939 // saved as complete-HTML.
940 index = 1;
941 }
942 // The option index is not zero-based.
943 DCHECK(index > 0 && index < 3);
944 param->dir = file_util::GetDirectoryFromPath(param->saved_main_file_path);
945
946 StringPrefMember save_file_path;
947 save_file_path.Init(prefs::kSaveFileDefaultDirectory, param->prefs, NULL);
948 // If user change the default saving directory, we will remember it just
949 // like IE and FireFox.
950 if (save_file_path.GetValue() != param->dir)
951 save_file_path.SetValue(param->dir);
952
953 param->save_type = (index == 1) ? SavePackage::SAVE_AS_ONLY_HTML :
954 SavePackage::SAVE_AS_COMPLETE_HTML;
955
956 if (param->save_type == SavePackage::SAVE_AS_COMPLETE_HTML) {
957 // Make new directory for saving complete file.
958 std::wstring file_name =
959 file_util::GetFilenameFromPath(param->saved_main_file_path);
960 std::wstring::size_type last_dot = file_name.rfind(L'.');
961 std::wstring pure_file_name;
962 if (last_dot == std::wstring::npos)
963 pure_file_name = file_name;
964 else
965 pure_file_name = std::wstring(file_name, 0, last_dot);
966 pure_file_name += L"_files";
967 file_util::AppendToPath(&param->dir, pure_file_name);
968 }
969
970 return true;
971 }
972
973 // Static.
974 bool SavePackage::GetBaseFileNameAndFileOrdinalNumber(
975 const std::wstring& file_name,
976 std::wstring* base_file_name,
977 std::wstring* file_ordinal_number) {
978 if (file_name.empty() || !base_file_name || !file_ordinal_number)
979 return false;
980
981 // Find dot position.
982 std::wstring::size_type dot_position = file_name.rfind(L".");
983 // Find position of right parenthesis.
984 std::wstring::size_type parenthesis_right;
985 if (std::wstring::npos == dot_position)
986 parenthesis_right = file_name.rfind(L')');
987 else
988 parenthesis_right = dot_position - 1;
989 // The latest character of pure file name is not ")", return false.
990 if (std::wstring::npos == parenthesis_right)
991 return false;
992 if (file_name.at(parenthesis_right) != L')')
993 return false;
994 // Find position of left parenthesis.
995 std::wstring::size_type parenthesis_left = file_name.rfind(L'(');
996 if (std::wstring::npos == parenthesis_left)
997 return false;
998
999 if (parenthesis_right <= parenthesis_left)
1000 return false;
1001 // Check whether content between left parenthesis and right parenthesis is
1002 // numeric or not.
1003 std::wstring ordinal_number(file_name, parenthesis_left + 1,
1004 parenthesis_right - parenthesis_left - 1);
1005 for (std::wstring::const_iterator cit = ordinal_number.begin();
1006 cit != ordinal_number.end(); ++cit)
1007 if (!IsAsciiDigit(*cit))
1008 return false;
1009
1010 *base_file_name = std::wstring(file_name, 0, parenthesis_left);
1011 *file_ordinal_number = ordinal_number;
1012 return true;
1013 }
1014
1015 // Static
1016 bool SavePackage::IsSavableURL(const GURL& url) {
1017 return url.SchemeIs("http") || url.SchemeIs("https") ||
1018 url.SchemeIs("file") || url.SchemeIs("ftp");
1019 }
1020
1021 // Static
1022 bool SavePackage::IsSavableContents(const std::string& contents_mime_type) {
1023 // WebKit creates Document object when MIME type is application/xhtml+xml,
1024 // so we also support this MIME type.
1025 return contents_mime_type == "text/html" ||
1026 contents_mime_type == "text/xml" ||
1027 contents_mime_type == "application/xhtml+xml" ||
1028 contents_mime_type == "text/plain";
1029 }
1030
1031 // Static
1032 bool SavePackage::CanSaveAsComplete(const std::string& contents_mime_type) {
1033 return contents_mime_type == "text/html";
1034 }
1035
1036 // Static
1037 bool SavePackage::GetSafePureFileName(const std::wstring& dir_path,
1038 const std::wstring& file_name_ext,
1039 uint32 max_file_path_len,
1040 std::wstring* pure_file_name) {
1041 DCHECK(!pure_file_name->empty());
1042 std::wstring final_name = dir_path;
1043 file_util::AppendToPath(&final_name, *pure_file_name);
1044 // Get total length of dir path, including ending "\".
1045 const std::wstring::size_type dir_path_length =
1046 final_name.length() - pure_file_name->length();
1047 // Get available length for putting dir path and pure file name.
1048 const std::wstring::size_type available_length =
1049 static_cast<std::wstring::size_type>(max_file_path_len) -
1050 file_name_ext.length();
1051
1052 if (final_name.length() <= available_length)
1053 return true;
1054
1055 if (available_length > dir_path_length) {
1056 *pure_file_name =
1057 pure_file_name->substr(0, available_length - dir_path_length);
1058 return true;
1059 } else {
1060 pure_file_name->clear();
1061 return false;
1062 }
1063 }
1064
OLDNEW
« no previous file with comments | « chrome/browser/save_package.h ('k') | chrome/browser/save_package_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698