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

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

Issue 7373004: Move the save file code from chrome to content. This is just a file move so the DEPS in content\b... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 9 years, 5 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
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/download/save_package.h"
6
7 #include <algorithm>
8
9 #include "base/file_path.h"
10 #include "base/file_util.h"
11 #include "base/i18n/file_util_icu.h"
12 #include "base/logging.h"
13 #include "base/message_loop.h"
14 #include "base/stl_util-inl.h"
15 #include "base/string_piece.h"
16 #include "base/string_split.h"
17 #include "base/sys_string_conversions.h"
18 #include "base/task.h"
19 #include "base/threading/thread.h"
20 #include "base/utf_string_conversions.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/download/download_item.h"
23 #include "chrome/browser/download/download_item_model.h"
24 #include "chrome/browser/download/download_manager.h"
25 #include "chrome/browser/download/download_prefs.h"
26 #include "chrome/browser/download/download_util.h"
27 #include "chrome/browser/download/save_file.h"
28 #include "chrome/browser/download/save_file_manager.h"
29 #include "chrome/browser/download/save_item.h"
30 #include "chrome/browser/net/url_fixer_upper.h"
31 #include "chrome/browser/platform_util.h"
32 #include "chrome/browser/prefs/pref_member.h"
33 #include "chrome/browser/prefs/pref_service.h"
34 #include "chrome/browser/profiles/profile.h"
35 #include "chrome/browser/tab_contents/tab_util.h"
36 #include "chrome/browser/ui/download/download_tab_helper.h"
37 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
38 #include "chrome/common/chrome_notification_types.h"
39 #include "chrome/common/chrome_paths.h"
40 #include "chrome/common/pref_names.h"
41 #include "chrome/common/render_messages.h"
42 #include "chrome/common/url_constants.h"
43 #include "content/browser/browser_thread.h"
44 #include "content/browser/renderer_host/render_process_host.h"
45 #include "content/browser/renderer_host/render_view_host.h"
46 #include "content/browser/renderer_host/render_view_host_delegate.h"
47 #include "content/browser/renderer_host/resource_dispatcher_host.h"
48 #include "content/browser/tab_contents/tab_contents.h"
49 #include "content/common/notification_service.h"
50 #include "grit/generated_resources.h"
51 #include "net/base/io_buffer.h"
52 #include "net/base/mime_util.h"
53 #include "net/base/net_util.h"
54 #include "net/url_request/url_request_context.h"
55 #include "third_party/WebKit/Source/WebKit/chromium/public/WebPageSerializerClie nt.h"
56 #include "ui/base/l10n/l10n_util.h"
57
58 using base::Time;
59 using WebKit::WebPageSerializerClient;
60
61 namespace {
62
63 // A counter for uniquely identifying each save package.
64 int g_save_package_id = 0;
65
66 // Default name which will be used when we can not get proper name from
67 // resource URL.
68 const char kDefaultSaveName[] = "saved_resource";
69
70 const FilePath::CharType kDefaultHtmlExtension[] =
71 #if defined(OS_WIN)
72 FILE_PATH_LITERAL("htm");
73 #else
74 FILE_PATH_LITERAL("html");
75 #endif
76
77 // Maximum number of file ordinal number. I think it's big enough for resolving
78 // name-conflict files which has same base file name.
79 const int32 kMaxFileOrdinalNumber = 9999;
80
81 // Maximum length for file path. Since Windows have MAX_PATH limitation for
82 // file path, we need to make sure length of file path of every saved file
83 // is less than MAX_PATH
84 #if defined(OS_WIN)
85 const uint32 kMaxFilePathLength = MAX_PATH - 1;
86 #elif defined(OS_POSIX)
87 const uint32 kMaxFilePathLength = PATH_MAX - 1;
88 #endif
89
90 // Maximum length for file ordinal number part. Since we only support the
91 // maximum 9999 for ordinal number, which means maximum file ordinal number part
92 // should be "(9998)", so the value is 6.
93 const uint32 kMaxFileOrdinalNumberPartLength = 6;
94
95 // If false, we don't prompt the user as to where to save the file. This
96 // exists only for testing.
97 bool g_should_prompt_for_filename = true;
98
99 // Indexes used for specifying which element in the extensions dropdown
100 // the user chooses when picking a save type.
101 const int kSelectFileHtmlOnlyIndex = 1;
102 const int kSelectFileCompleteIndex = 2;
103
104 // Used for mapping between SavePackageType constants and the indexes above.
105 const SavePackage::SavePackageType kIndexToSaveType[] = {
106 SavePackage::SAVE_TYPE_UNKNOWN,
107 SavePackage::SAVE_AS_ONLY_HTML,
108 SavePackage::SAVE_AS_COMPLETE_HTML,
109 };
110
111 // Used for mapping between the IDS_ string identifiers and the indexes above.
112 const int kIndexToIDS[] = {
113 0, IDS_SAVE_PAGE_DESC_HTML_ONLY, IDS_SAVE_PAGE_DESC_COMPLETE,
114 };
115
116 int SavePackageTypeToIndex(SavePackage::SavePackageType type) {
117 for (size_t i = 0; i < arraysize(kIndexToSaveType); ++i) {
118 if (kIndexToSaveType[i] == type)
119 return i;
120 }
121 NOTREACHED();
122 return -1;
123 }
124
125 // Strip current ordinal number, if any. Should only be used on pure
126 // file names, i.e. those stripped of their extensions.
127 // TODO(estade): improve this to not choke on alternate encodings.
128 FilePath::StringType StripOrdinalNumber(
129 const FilePath::StringType& pure_file_name) {
130 FilePath::StringType::size_type r_paren_index =
131 pure_file_name.rfind(FILE_PATH_LITERAL(')'));
132 FilePath::StringType::size_type l_paren_index =
133 pure_file_name.rfind(FILE_PATH_LITERAL('('));
134 if (l_paren_index >= r_paren_index)
135 return pure_file_name;
136
137 for (FilePath::StringType::size_type i = l_paren_index + 1;
138 i != r_paren_index; ++i) {
139 if (!IsAsciiDigit(pure_file_name[i]))
140 return pure_file_name;
141 }
142
143 return pure_file_name.substr(0, l_paren_index);
144 }
145
146 // Check whether we can save page as complete-HTML for the contents which
147 // have specified a MIME type. Now only contents which have the MIME type
148 // "text/html" can be saved as complete-HTML.
149 bool CanSaveAsComplete(const std::string& contents_mime_type) {
150 return contents_mime_type == "text/html" ||
151 contents_mime_type == "application/xhtml+xml";
152 }
153
154 } // namespace
155
156 SavePackage::SavePackage(TabContentsWrapper* wrapper,
157 SavePackageType save_type,
158 const FilePath& file_full_path,
159 const FilePath& directory_full_path)
160 : TabContentsObserver(wrapper->tab_contents()),
161 wrapper_(wrapper),
162 file_manager_(NULL),
163 download_(NULL),
164 page_url_(GetUrlToBeSaved()),
165 saved_main_file_path_(file_full_path),
166 saved_main_directory_path_(directory_full_path),
167 title_(tab_contents()->GetTitle()),
168 finished_(false),
169 user_canceled_(false),
170 disk_error_occurred_(false),
171 save_type_(save_type),
172 all_save_items_count_(0),
173 wait_state_(INITIALIZE),
174 tab_id_(tab_contents()->GetRenderProcessHost()->id()),
175 unique_id_(g_save_package_id++),
176 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
177 DCHECK(page_url_.is_valid());
178 DCHECK(save_type_ == SAVE_AS_ONLY_HTML ||
179 save_type_ == SAVE_AS_COMPLETE_HTML);
180 DCHECK(!saved_main_file_path_.empty() &&
181 saved_main_file_path_.value().length() <= kMaxFilePathLength);
182 DCHECK(!saved_main_directory_path_.empty() &&
183 saved_main_directory_path_.value().length() < kMaxFilePathLength);
184 InternalInit();
185 }
186
187 SavePackage::SavePackage(TabContentsWrapper* wrapper)
188 : TabContentsObserver(wrapper->tab_contents()),
189 wrapper_(wrapper),
190 file_manager_(NULL),
191 download_(NULL),
192 page_url_(GetUrlToBeSaved()),
193 title_(tab_contents()->GetTitle()),
194 finished_(false),
195 user_canceled_(false),
196 disk_error_occurred_(false),
197 save_type_(SAVE_TYPE_UNKNOWN),
198 all_save_items_count_(0),
199 wait_state_(INITIALIZE),
200 tab_id_(tab_contents()->GetRenderProcessHost()->id()),
201 unique_id_(g_save_package_id++),
202 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
203 DCHECK(page_url_.is_valid());
204 InternalInit();
205 }
206
207 // This is for testing use. Set |finished_| as true because we don't want
208 // method Cancel to be be called in destructor in test mode.
209 // We also don't call InternalInit().
210 SavePackage::SavePackage(TabContentsWrapper* wrapper,
211 const FilePath& file_full_path,
212 const FilePath& directory_full_path)
213 : TabContentsObserver(wrapper->tab_contents()),
214 wrapper_(wrapper),
215 file_manager_(NULL),
216 download_(NULL),
217 saved_main_file_path_(file_full_path),
218 saved_main_directory_path_(directory_full_path),
219 finished_(true),
220 user_canceled_(false),
221 disk_error_occurred_(false),
222 save_type_(SAVE_TYPE_UNKNOWN),
223 all_save_items_count_(0),
224 wait_state_(INITIALIZE),
225 tab_id_(0),
226 unique_id_(g_save_package_id++),
227 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
228 }
229
230 SavePackage::~SavePackage() {
231 // Stop receiving saving job's updates
232 if (!finished_ && !canceled()) {
233 // Unexpected quit.
234 Cancel(true);
235 }
236
237 DCHECK(all_save_items_count_ == (waiting_item_queue_.size() +
238 completed_count() +
239 in_process_count()));
240 // Free all SaveItems.
241 while (!waiting_item_queue_.empty()) {
242 // We still have some items which are waiting for start to save.
243 SaveItem* save_item = waiting_item_queue_.front();
244 waiting_item_queue_.pop();
245 delete save_item;
246 }
247
248 STLDeleteValues(&saved_success_items_);
249 STLDeleteValues(&in_progress_items_);
250 STLDeleteValues(&saved_failed_items_);
251
252 // The DownloadItem is owned by DownloadManager.
253 download_ = NULL;
254
255 file_manager_ = NULL;
256
257 // If there's an outstanding save dialog, make sure it doesn't call us back
258 // now that we're gone.
259 if (select_file_dialog_.get())
260 select_file_dialog_->ListenerDestroyed();
261 }
262
263 // Retrieves the URL to be saved from tab_contents_ variable.
264 GURL SavePackage::GetUrlToBeSaved() {
265 // Instead of using tab_contents_.GetURL here, we use url()
266 // (which is the "real" url of the page)
267 // from the NavigationEntry because it reflects its' origin
268 // rather than the displayed one (returned by GetURL) which may be
269 // different (like having "view-source:" on the front).
270 NavigationEntry* active_entry =
271 tab_contents()->controller().GetActiveEntry();
272 return active_entry->url();
273 }
274
275 // Cancel all in progress request, might be called by user or internal error.
276 void SavePackage::Cancel(bool user_action) {
277 if (!canceled()) {
278 if (user_action)
279 user_canceled_ = true;
280 else
281 disk_error_occurred_ = true;
282 Stop();
283 }
284 }
285
286 // Init() can be called directly, or indirectly via GetSaveInfo(). In both
287 // cases, we need file_manager_ to be initialized, so we do this first.
288 void SavePackage::InternalInit() {
289 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
290 if (!rdh) {
291 NOTREACHED();
292 return;
293 }
294
295 file_manager_ = rdh->save_file_manager();
296 if (!file_manager_) {
297 NOTREACHED();
298 return;
299 }
300 }
301
302 // Initialize the SavePackage.
303 bool SavePackage::Init() {
304 // Set proper running state.
305 if (wait_state_ != INITIALIZE)
306 return false;
307
308 wait_state_ = START_PROCESS;
309
310 // Initialize the request context and resource dispatcher.
311 Profile* profile = tab_contents()->profile();
312 if (!profile) {
313 NOTREACHED();
314 return false;
315 }
316
317 // Create the fake DownloadItem and display the view.
318 DownloadManager* download_manager =
319 tab_contents()->profile()->GetDownloadManager();
320 download_ = new DownloadItem(download_manager,
321 saved_main_file_path_,
322 page_url_,
323 profile->IsOffTheRecord());
324
325 // Transfer the ownership to the download manager. We need the DownloadItem
326 // to be alive as long as the Profile is alive.
327 download_manager->SavePageAsDownloadStarted(download_);
328
329 wrapper_->download_tab_helper()->OnStartDownload(download_);
330
331 // Check save type and process the save page job.
332 if (save_type_ == SAVE_AS_COMPLETE_HTML) {
333 // Get directory
334 DCHECK(!saved_main_directory_path_.empty());
335 GetAllSavableResourceLinksForCurrentPage();
336 } else {
337 wait_state_ = NET_FILES;
338 SaveFileCreateInfo::SaveFileSource save_source = page_url_.SchemeIsFile() ?
339 SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
340 SaveFileCreateInfo::SAVE_FILE_FROM_NET;
341 SaveItem* save_item = new SaveItem(page_url_,
342 GURL(),
343 this,
344 save_source);
345 // Add this item to waiting list.
346 waiting_item_queue_.push(save_item);
347 all_save_items_count_ = 1;
348 download_->set_total_bytes(1);
349
350 DoSavingProcess();
351 }
352
353 return true;
354 }
355
356 // On POSIX, the length of |pure_file_name| + |file_name_ext| is further
357 // restricted by NAME_MAX. The maximum allowed path looks like:
358 // '/path/to/save_dir' + '/' + NAME_MAX.
359 uint32 SavePackage::GetMaxPathLengthForDirectory(const FilePath& base_dir) {
360 #if defined(OS_POSIX)
361 return std::min(kMaxFilePathLength,
362 static_cast<uint32>(base_dir.value().length()) +
363 NAME_MAX + 1);
364 #else
365 return kMaxFilePathLength;
366 #endif
367 }
368
369 // File name is considered being consist of pure file name, dot and file
370 // extension name. File name might has no dot and file extension, or has
371 // multiple dot inside file name. The dot, which separates the pure file
372 // name and file extension name, is last dot in the whole file name.
373 // This function is for making sure the length of specified file path is not
374 // great than the specified maximum length of file path and getting safe pure
375 // file name part if the input pure file name is too long.
376 // The parameter |dir_path| specifies directory part of the specified
377 // file path. The parameter |file_name_ext| specifies file extension
378 // name part of the specified file path (including start dot). The parameter
379 // |max_file_path_len| specifies maximum length of the specified file path.
380 // The parameter |pure_file_name| input pure file name part of the specified
381 // file path. If the length of specified file path is great than
382 // |max_file_path_len|, the |pure_file_name| will output new pure file name
383 // part for making sure the length of specified file path is less than
384 // specified maximum length of file path. Return false if the function can
385 // not get a safe pure file name, otherwise it returns true.
386 bool SavePackage::GetSafePureFileName(const FilePath& dir_path,
387 const FilePath::StringType& file_name_ext,
388 uint32 max_file_path_len,
389 FilePath::StringType* pure_file_name) {
390 DCHECK(!pure_file_name->empty());
391 int available_length = static_cast<int>(max_file_path_len -
392 dir_path.value().length() -
393 file_name_ext.length());
394 // Need an extra space for the separator.
395 if (!file_util::EndsWithSeparator(dir_path))
396 --available_length;
397
398 // Plenty of room.
399 if (static_cast<int>(pure_file_name->length()) <= available_length)
400 return true;
401
402 // Limited room. Truncate |pure_file_name| to fit.
403 if (available_length > 0) {
404 *pure_file_name = pure_file_name->substr(0, available_length);
405 return true;
406 }
407
408 // Not enough room to even use a shortened |pure_file_name|.
409 pure_file_name->clear();
410 return false;
411 }
412
413 // Generate name for saving resource.
414 bool SavePackage::GenerateFileName(const std::string& disposition,
415 const GURL& url,
416 bool need_html_ext,
417 FilePath::StringType* generated_name) {
418 // TODO(jungshik): Figure out the referrer charset when having one
419 // makes sense and pass it to GetSuggestedFilename.
420 string16 suggested_name =
421 net::GetSuggestedFilename(url, disposition, "", "",
422 ASCIIToUTF16(kDefaultSaveName));
423
424 // TODO(evan): this code is totally wrong -- we should just generate
425 // Unicode filenames and do all this encoding switching at the end.
426 // However, I'm just shuffling wrong code around, at least not adding
427 // to it.
428 #if defined(OS_WIN)
429 FilePath file_path = FilePath(suggested_name);
430 #else
431 FilePath file_path = FilePath(
432 base::SysWideToNativeMB(UTF16ToWide(suggested_name)));
433 #endif
434
435 DCHECK(!file_path.empty());
436 FilePath::StringType pure_file_name =
437 file_path.RemoveExtension().BaseName().value();
438 FilePath::StringType file_name_ext = file_path.Extension();
439
440 // If it is HTML resource, use ".htm{l,}" as its extension.
441 if (need_html_ext) {
442 file_name_ext = FILE_PATH_LITERAL(".");
443 file_name_ext.append(kDefaultHtmlExtension);
444 }
445
446 // Need to make sure the suggested file name is not too long.
447 uint32 max_path = GetMaxPathLengthForDirectory(saved_main_directory_path_);
448
449 // Get safe pure file name.
450 if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
451 max_path, &pure_file_name))
452 return false;
453
454 FilePath::StringType file_name = pure_file_name + file_name_ext;
455
456 // Check whether we already have same name.
457 if (file_name_set_.find(file_name) == file_name_set_.end()) {
458 file_name_set_.insert(file_name);
459 } else {
460 // Found same name, increase the ordinal number for the file name.
461 FilePath::StringType base_file_name = StripOrdinalNumber(pure_file_name);
462
463 // We need to make sure the length of base file name plus maximum ordinal
464 // number path will be less than or equal to kMaxFilePathLength.
465 if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
466 max_path - kMaxFileOrdinalNumberPartLength, &base_file_name))
467 return false;
468
469 // Prepare the new ordinal number.
470 uint32 ordinal_number;
471 FileNameCountMap::iterator it = file_name_count_map_.find(base_file_name);
472 if (it == file_name_count_map_.end()) {
473 // First base-name-conflict resolving, use 1 as initial ordinal number.
474 file_name_count_map_[base_file_name] = 1;
475 ordinal_number = 1;
476 } else {
477 // We have met same base-name conflict, use latest ordinal number.
478 ordinal_number = it->second;
479 }
480
481 if (ordinal_number > (kMaxFileOrdinalNumber - 1)) {
482 // Use a random file from temporary file.
483 FilePath temp_file;
484 file_util::CreateTemporaryFile(&temp_file);
485 file_name = temp_file.RemoveExtension().BaseName().value();
486 // Get safe pure file name.
487 if (!GetSafePureFileName(saved_main_directory_path_,
488 FilePath::StringType(),
489 max_path, &file_name))
490 return false;
491 } else {
492 for (int i = ordinal_number; i < kMaxFileOrdinalNumber; ++i) {
493 FilePath::StringType new_name = base_file_name +
494 StringPrintf(FILE_PATH_LITERAL("(%d)"), i) + file_name_ext;
495 if (file_name_set_.find(new_name) == file_name_set_.end()) {
496 // Resolved name conflict.
497 file_name = new_name;
498 file_name_count_map_[base_file_name] = ++i;
499 break;
500 }
501 }
502 }
503
504 file_name_set_.insert(file_name);
505 }
506
507 DCHECK(!file_name.empty());
508 generated_name->assign(file_name);
509
510 return true;
511 }
512
513 // We have received a message from SaveFileManager about a new saving job. We
514 // create a SaveItem and store it in our in_progress list.
515 void SavePackage::StartSave(const SaveFileCreateInfo* info) {
516 DCHECK(info && !info->url.is_empty());
517
518 SaveUrlItemMap::iterator it = in_progress_items_.find(info->url.spec());
519 if (it == in_progress_items_.end()) {
520 // If not found, we must have cancel action.
521 DCHECK(canceled());
522 return;
523 }
524 SaveItem* save_item = it->second;
525
526 DCHECK(!saved_main_file_path_.empty());
527
528 save_item->SetSaveId(info->save_id);
529 save_item->SetTotalBytes(info->total_bytes);
530
531 // Determine the proper path for a saving job, by choosing either the default
532 // save directory, or prompting the user.
533 DCHECK(!save_item->has_final_name());
534 if (info->url != page_url_) {
535 FilePath::StringType generated_name;
536 // For HTML resource file, make sure it will have .htm as extension name,
537 // otherwise, when you open the saved page in Chrome again, download
538 // file manager will treat it as downloadable resource, and download it
539 // instead of opening it as HTML.
540 bool need_html_ext =
541 info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM;
542 if (!GenerateFileName(info->content_disposition,
543 GURL(info->url),
544 need_html_ext,
545 &generated_name)) {
546 // We can not generate file name for this SaveItem, so we cancel the
547 // saving page job if the save source is from serialized DOM data.
548 // Otherwise, it means this SaveItem is sub-resource type, we treat it
549 // as an error happened on saving. We can ignore this type error for
550 // sub-resource links which will be resolved as absolute links instead
551 // of local links in final saved contents.
552 if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM)
553 Cancel(true);
554 else
555 SaveFinished(save_item->save_id(), 0, false);
556 return;
557 }
558
559 // When saving page as only-HTML, we only have a SaveItem whose url
560 // must be page_url_.
561 DCHECK(save_type_ == SAVE_AS_COMPLETE_HTML);
562 DCHECK(!saved_main_directory_path_.empty());
563
564 // Now we get final name retrieved from GenerateFileName, we will use it
565 // rename the SaveItem.
566 FilePath final_name = saved_main_directory_path_.Append(generated_name);
567 save_item->Rename(final_name);
568 } else {
569 // It is the main HTML file, use the name chosen by the user.
570 save_item->Rename(saved_main_file_path_);
571 }
572
573 // If the save source is from file system, inform SaveFileManager to copy
574 // corresponding file to the file path which this SaveItem specifies.
575 if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) {
576 BrowserThread::PostTask(
577 BrowserThread::FILE, FROM_HERE,
578 NewRunnableMethod(file_manager_,
579 &SaveFileManager::SaveLocalFile,
580 save_item->url(),
581 save_item->save_id(),
582 tab_id()));
583 return;
584 }
585
586 // Check whether we begin to require serialized HTML data.
587 if (save_type_ == SAVE_AS_COMPLETE_HTML && wait_state_ == HTML_DATA) {
588 // Inform backend to serialize the all frames' DOM and send serialized
589 // HTML data back.
590 GetSerializedHtmlDataForCurrentPageWithLocalLinks();
591 }
592 }
593
594 // Look up SaveItem by save id from in progress map.
595 SaveItem* SavePackage::LookupItemInProcessBySaveId(int32 save_id) {
596 if (in_process_count()) {
597 for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
598 it != in_progress_items_.end(); ++it) {
599 SaveItem* save_item = it->second;
600 DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
601 if (save_item->save_id() == save_id)
602 return save_item;
603 }
604 }
605 return NULL;
606 }
607
608 // Remove SaveItem from in progress map and put it to saved map.
609 void SavePackage::PutInProgressItemToSavedMap(SaveItem* save_item) {
610 SaveUrlItemMap::iterator it = in_progress_items_.find(
611 save_item->url().spec());
612 DCHECK(it != in_progress_items_.end());
613 DCHECK(save_item == it->second);
614 in_progress_items_.erase(it);
615
616 if (save_item->success()) {
617 // Add it to saved_success_items_.
618 DCHECK(saved_success_items_.find(save_item->save_id()) ==
619 saved_success_items_.end());
620 saved_success_items_[save_item->save_id()] = save_item;
621 } else {
622 // Add it to saved_failed_items_.
623 DCHECK(saved_failed_items_.find(save_item->url().spec()) ==
624 saved_failed_items_.end());
625 saved_failed_items_[save_item->url().spec()] = save_item;
626 }
627 }
628
629 // Called for updating saving state.
630 bool SavePackage::UpdateSaveProgress(int32 save_id,
631 int64 size,
632 bool write_success) {
633 // Because we might have canceled this saving job before,
634 // so we might not find corresponding SaveItem.
635 SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
636 if (!save_item)
637 return false;
638
639 save_item->Update(size);
640
641 // If we got disk error, cancel whole save page job.
642 if (!write_success) {
643 // Cancel job with reason of disk error.
644 Cancel(false);
645 }
646 return true;
647 }
648
649 // Stop all page saving jobs that are in progress and instruct the file thread
650 // to delete all saved files.
651 void SavePackage::Stop() {
652 // If we haven't moved out of the initial state, there's nothing to cancel and
653 // there won't be valid pointers for file_manager_ or download_.
654 if (wait_state_ == INITIALIZE)
655 return;
656
657 // When stopping, if it still has some items in in_progress, cancel them.
658 DCHECK(canceled());
659 if (in_process_count()) {
660 SaveUrlItemMap::iterator it = in_progress_items_.begin();
661 for (; it != in_progress_items_.end(); ++it) {
662 SaveItem* save_item = it->second;
663 DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
664 save_item->Cancel();
665 }
666 // Remove all in progress item to saved map. For failed items, they will
667 // be put into saved_failed_items_, for successful item, they will be put
668 // into saved_success_items_.
669 while (in_process_count())
670 PutInProgressItemToSavedMap(in_progress_items_.begin()->second);
671 }
672
673 // This vector contains the save ids of the save files which SaveFileManager
674 // needs to remove from its save_file_map_.
675 SaveIDList save_ids;
676 for (SavedItemMap::iterator it = saved_success_items_.begin();
677 it != saved_success_items_.end(); ++it)
678 save_ids.push_back(it->first);
679 for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
680 it != saved_failed_items_.end(); ++it)
681 save_ids.push_back(it->second->save_id());
682
683 BrowserThread::PostTask(
684 BrowserThread::FILE, FROM_HERE,
685 NewRunnableMethod(file_manager_,
686 &SaveFileManager::RemoveSavedFileFromFileMap,
687 save_ids));
688
689 finished_ = true;
690 wait_state_ = FAILED;
691
692 // Inform the DownloadItem we have canceled whole save page job.
693 download_->Cancel(false);
694 }
695
696 void SavePackage::CheckFinish() {
697 if (in_process_count() || finished_)
698 return;
699
700 FilePath dir = (save_type_ == SAVE_AS_COMPLETE_HTML &&
701 saved_success_items_.size() > 1) ?
702 saved_main_directory_path_ : FilePath();
703
704 // This vector contains the final names of all the successfully saved files
705 // along with their save ids. It will be passed to SaveFileManager to do the
706 // renaming job.
707 FinalNameList final_names;
708 for (SavedItemMap::iterator it = saved_success_items_.begin();
709 it != saved_success_items_.end(); ++it)
710 final_names.push_back(std::make_pair(it->first,
711 it->second->full_path()));
712
713 BrowserThread::PostTask(
714 BrowserThread::FILE, FROM_HERE,
715 NewRunnableMethod(file_manager_,
716 &SaveFileManager::RenameAllFiles,
717 final_names,
718 dir,
719 tab_contents()->GetRenderProcessHost()->id(),
720 tab_contents()->render_view_host()->routing_id(),
721 id()));
722 }
723
724 // Successfully finished all items of this SavePackage.
725 void SavePackage::Finish() {
726 // User may cancel the job when we're moving files to the final directory.
727 if (canceled())
728 return;
729
730 wait_state_ = SUCCESSFUL;
731 finished_ = true;
732
733 // This vector contains the save ids of the save files which SaveFileManager
734 // needs to remove from its save_file_map_.
735 SaveIDList save_ids;
736 for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
737 it != saved_failed_items_.end(); ++it)
738 save_ids.push_back(it->second->save_id());
739
740 BrowserThread::PostTask(
741 BrowserThread::FILE, FROM_HERE,
742 NewRunnableMethod(file_manager_,
743 &SaveFileManager::RemoveSavedFileFromFileMap,
744 save_ids));
745
746 download_->OnAllDataSaved(all_save_items_count_);
747 download_->MarkAsComplete();
748
749 NotificationService::current()->Notify(
750 chrome::NOTIFICATION_SAVE_PACKAGE_SUCCESSFULLY_FINISHED,
751 Source<SavePackage>(this),
752 Details<GURL>(&page_url_));
753 }
754
755 // Called for updating end state.
756 void SavePackage::SaveFinished(int32 save_id, int64 size, bool is_success) {
757 // Because we might have canceled this saving job before,
758 // so we might not find corresponding SaveItem. Just ignore it.
759 SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
760 if (!save_item)
761 return;
762
763 // Let SaveItem set end state.
764 save_item->Finish(size, is_success);
765 // Remove the associated save id and SavePackage.
766 file_manager_->RemoveSaveFile(save_id, save_item->url(), this);
767
768 PutInProgressItemToSavedMap(save_item);
769
770 // Inform the DownloadItem to update UI.
771 // We use the received bytes as number of saved files.
772 download_->Update(completed_count());
773
774 if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM &&
775 save_item->url() == page_url_ && !save_item->received_bytes()) {
776 // If size of main HTML page is 0, treat it as disk error.
777 Cancel(false);
778 return;
779 }
780
781 if (canceled()) {
782 DCHECK(finished_);
783 return;
784 }
785
786 // Continue processing the save page job.
787 DoSavingProcess();
788
789 // Check whether we can successfully finish whole job.
790 CheckFinish();
791 }
792
793 // Sometimes, the net io will only call SaveFileManager::SaveFinished with
794 // save id -1 when it encounters error. Since in this case, save id will be
795 // -1, so we can only use URL to find which SaveItem is associated with
796 // this error.
797 // Saving an item failed. If it's a sub-resource, ignore it. If the error comes
798 // from serializing HTML data, then cancel saving page.
799 void SavePackage::SaveFailed(const GURL& save_url) {
800 SaveUrlItemMap::iterator it = in_progress_items_.find(save_url.spec());
801 if (it == in_progress_items_.end()) {
802 NOTREACHED(); // Should not exist!
803 return;
804 }
805 SaveItem* save_item = it->second;
806
807 save_item->Finish(0, false);
808
809 PutInProgressItemToSavedMap(save_item);
810
811 // Inform the DownloadItem to update UI.
812 // We use the received bytes as number of saved files.
813 download_->Update(completed_count());
814
815 if (save_type_ == SAVE_AS_ONLY_HTML ||
816 save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
817 // We got error when saving page. Treat it as disk error.
818 Cancel(true);
819 }
820
821 if (canceled()) {
822 DCHECK(finished_);
823 return;
824 }
825
826 // Continue processing the save page job.
827 DoSavingProcess();
828
829 CheckFinish();
830 }
831
832 void SavePackage::SaveCanceled(SaveItem* save_item) {
833 // Call the RemoveSaveFile in UI thread.
834 file_manager_->RemoveSaveFile(save_item->save_id(),
835 save_item->url(),
836 this);
837 if (save_item->save_id() != -1)
838 BrowserThread::PostTask(
839 BrowserThread::FILE, FROM_HERE,
840 NewRunnableMethod(file_manager_,
841 &SaveFileManager::CancelSave,
842 save_item->save_id()));
843 }
844
845 // Initiate a saving job of a specific URL. We send the request to
846 // SaveFileManager, which will dispatch it to different approach according to
847 // the save source. Parameter process_all_remaining_items indicates whether
848 // we need to save all remaining items.
849 void SavePackage::SaveNextFile(bool process_all_remaining_items) {
850 DCHECK(tab_contents());
851 DCHECK(waiting_item_queue_.size());
852
853 do {
854 // Pop SaveItem from waiting list.
855 SaveItem* save_item = waiting_item_queue_.front();
856 waiting_item_queue_.pop();
857
858 // Add the item to in_progress_items_.
859 SaveUrlItemMap::iterator it = in_progress_items_.find(
860 save_item->url().spec());
861 DCHECK(it == in_progress_items_.end());
862 in_progress_items_[save_item->url().spec()] = save_item;
863 save_item->Start();
864 file_manager_->SaveURL(save_item->url(),
865 save_item->referrer(),
866 tab_contents()->GetRenderProcessHost()->id(),
867 routing_id(),
868 save_item->save_source(),
869 save_item->full_path(),
870 tab_contents()->profile()->GetResourceContext(),
871 this);
872 } while (process_all_remaining_items && waiting_item_queue_.size());
873 }
874
875
876 // Open download page in windows explorer on file thread, to avoid blocking the
877 // user interface.
878 void SavePackage::ShowDownloadInShell() {
879 DCHECK(file_manager_);
880 DCHECK(finished_ && !canceled() && !saved_main_file_path_.empty());
881 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
882 #if defined(OS_MACOSX)
883 // Mac OS X requires opening downloads on the UI thread.
884 platform_util::ShowItemInFolder(saved_main_file_path_);
885 #else
886 BrowserThread::PostTask(
887 BrowserThread::FILE, FROM_HERE,
888 NewRunnableMethod(file_manager_,
889 &SaveFileManager::OnShowSavedFileInShell,
890 saved_main_file_path_));
891 #endif
892 }
893
894 // Calculate the percentage of whole save page job.
895 int SavePackage::PercentComplete() {
896 if (!all_save_items_count_)
897 return 0;
898 else if (!in_process_count())
899 return 100;
900 else
901 return completed_count() / all_save_items_count_;
902 }
903
904 // Continue processing the save page job after one SaveItem has been
905 // finished.
906 void SavePackage::DoSavingProcess() {
907 if (save_type_ == SAVE_AS_COMPLETE_HTML) {
908 // We guarantee that images and JavaScripts must be downloaded first.
909 // So when finishing all those sub-resources, we will know which
910 // sub-resource's link can be replaced with local file path, which
911 // sub-resource's link need to be replaced with absolute URL which
912 // point to its internet address because it got error when saving its data.
913 SaveItem* save_item = NULL;
914 // Start a new SaveItem job if we still have job in waiting queue.
915 if (waiting_item_queue_.size()) {
916 DCHECK(wait_state_ == NET_FILES);
917 save_item = waiting_item_queue_.front();
918 if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
919 SaveNextFile(false);
920 } else if (!in_process_count()) {
921 // If there is no in-process SaveItem, it means all sub-resources
922 // have been processed. Now we need to start serializing HTML DOM
923 // for the current page to get the generated HTML data.
924 wait_state_ = HTML_DATA;
925 // All non-HTML resources have been finished, start all remaining
926 // HTML files.
927 SaveNextFile(true);
928 }
929 } else if (in_process_count()) {
930 // Continue asking for HTML data.
931 DCHECK(wait_state_ == HTML_DATA);
932 }
933 } else {
934 // Save as HTML only.
935 DCHECK(wait_state_ == NET_FILES);
936 DCHECK(save_type_ == SAVE_AS_ONLY_HTML);
937 if (waiting_item_queue_.size()) {
938 DCHECK(all_save_items_count_ == waiting_item_queue_.size());
939 SaveNextFile(false);
940 }
941 }
942 }
943
944 bool SavePackage::OnMessageReceived(const IPC::Message& message) {
945 bool handled = true;
946 IPC_BEGIN_MESSAGE_MAP(SavePackage, message)
947 IPC_MESSAGE_HANDLER(ViewHostMsg_SendCurrentPageAllSavableResourceLinks,
948 OnReceivedSavableResourceLinksForCurrentPage)
949 IPC_MESSAGE_HANDLER(ViewHostMsg_SendSerializedHtmlData,
950 OnReceivedSerializedHtmlData)
951 IPC_MESSAGE_UNHANDLED(handled = false)
952 IPC_END_MESSAGE_MAP()
953 return handled;
954 }
955
956 // After finishing all SaveItems which need to get data from net.
957 // We collect all URLs which have local storage and send the
958 // map:(originalURL:currentLocalPath) to render process (backend).
959 // Then render process will serialize DOM and send data to us.
960 void SavePackage::GetSerializedHtmlDataForCurrentPageWithLocalLinks() {
961 if (wait_state_ != HTML_DATA)
962 return;
963 std::vector<GURL> saved_links;
964 std::vector<FilePath> saved_file_paths;
965 int successful_started_items_count = 0;
966
967 // Collect all saved items which have local storage.
968 // First collect the status of all the resource files and check whether they
969 // have created local files although they have not been completely saved.
970 // If yes, the file can be saved. Otherwise, there is a disk error, so we
971 // need to cancel the page saving job.
972 for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
973 it != in_progress_items_.end(); ++it) {
974 DCHECK(it->second->save_source() ==
975 SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
976 if (it->second->has_final_name())
977 successful_started_items_count++;
978 saved_links.push_back(it->second->url());
979 saved_file_paths.push_back(it->second->file_name());
980 }
981
982 // If not all file of HTML resource have been started, then wait.
983 if (successful_started_items_count != in_process_count())
984 return;
985
986 // Collect all saved success items.
987 for (SavedItemMap::iterator it = saved_success_items_.begin();
988 it != saved_success_items_.end(); ++it) {
989 DCHECK(it->second->has_final_name());
990 saved_links.push_back(it->second->url());
991 saved_file_paths.push_back(it->second->file_name());
992 }
993
994 // Get the relative directory name.
995 FilePath relative_dir_name = saved_main_directory_path_.BaseName();
996
997 Send(new ViewMsg_GetSerializedHtmlDataForCurrentPageWithLocalLinks(
998 routing_id(), saved_links, saved_file_paths, relative_dir_name));
999 }
1000
1001 // Process the serialized HTML content data of a specified web page
1002 // retrieved from render process.
1003 void SavePackage::OnReceivedSerializedHtmlData(const GURL& frame_url,
1004 const std::string& data,
1005 int32 status) {
1006 WebPageSerializerClient::PageSerializationStatus flag =
1007 static_cast<WebPageSerializerClient::PageSerializationStatus>(status);
1008 // Check current state.
1009 if (wait_state_ != HTML_DATA)
1010 return;
1011
1012 int id = tab_id();
1013 // If the all frames are finished saving, we need to close the
1014 // remaining SaveItems.
1015 if (flag == WebPageSerializerClient::AllFramesAreFinished) {
1016 for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
1017 it != in_progress_items_.end(); ++it) {
1018 VLOG(20) << " " << __FUNCTION__ << "()"
1019 << " save_id = " << it->second->save_id()
1020 << " url = \"" << it->second->url().spec() << "\"";
1021 BrowserThread::PostTask(
1022 BrowserThread::FILE, FROM_HERE,
1023 NewRunnableMethod(file_manager_,
1024 &SaveFileManager::SaveFinished,
1025 it->second->save_id(),
1026 it->second->url(),
1027 id,
1028 true));
1029 }
1030 return;
1031 }
1032
1033 SaveUrlItemMap::iterator it = in_progress_items_.find(frame_url.spec());
1034 if (it == in_progress_items_.end())
1035 return;
1036 SaveItem* save_item = it->second;
1037 DCHECK(save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
1038
1039 if (!data.empty()) {
1040 // Prepare buffer for saving HTML data.
1041 scoped_refptr<net::IOBuffer> new_data(new net::IOBuffer(data.size()));
1042 memcpy(new_data->data(), data.data(), data.size());
1043
1044 // Call write file functionality in file thread.
1045 BrowserThread::PostTask(
1046 BrowserThread::FILE, FROM_HERE,
1047 NewRunnableMethod(file_manager_,
1048 &SaveFileManager::UpdateSaveProgress,
1049 save_item->save_id(),
1050 new_data,
1051 static_cast<int>(data.size())));
1052 }
1053
1054 // Current frame is completed saving, call finish in file thread.
1055 if (flag == WebPageSerializerClient::CurrentFrameIsFinished) {
1056 VLOG(20) << " " << __FUNCTION__ << "()"
1057 << " save_id = " << save_item->save_id()
1058 << " url = \"" << save_item->url().spec() << "\"";
1059 BrowserThread::PostTask(
1060 BrowserThread::FILE, FROM_HERE,
1061 NewRunnableMethod(file_manager_,
1062 &SaveFileManager::SaveFinished,
1063 save_item->save_id(),
1064 save_item->url(),
1065 id,
1066 true));
1067 }
1068 }
1069
1070 // Ask for all savable resource links from backend, include main frame and
1071 // sub-frame.
1072 void SavePackage::GetAllSavableResourceLinksForCurrentPage() {
1073 if (wait_state_ != START_PROCESS)
1074 return;
1075
1076 wait_state_ = RESOURCES_LIST;
1077 Send(new ViewMsg_GetAllSavableResourceLinksForCurrentPage(routing_id(),
1078 page_url_));
1079 }
1080
1081 // Give backend the lists which contain all resource links that have local
1082 // storage, after which, render process will serialize DOM for generating
1083 // HTML data.
1084 void SavePackage::OnReceivedSavableResourceLinksForCurrentPage(
1085 const std::vector<GURL>& resources_list,
1086 const std::vector<GURL>& referrers_list,
1087 const std::vector<GURL>& frames_list) {
1088 if (wait_state_ != RESOURCES_LIST)
1089 return;
1090
1091 DCHECK(resources_list.size() == referrers_list.size());
1092 all_save_items_count_ = static_cast<int>(resources_list.size()) +
1093 static_cast<int>(frames_list.size());
1094
1095 // We use total bytes as the total number of files we want to save.
1096 download_->set_total_bytes(all_save_items_count_);
1097
1098 if (all_save_items_count_) {
1099 // Put all sub-resources to wait list.
1100 for (int i = 0; i < static_cast<int>(resources_list.size()); ++i) {
1101 const GURL& u = resources_list[i];
1102 DCHECK(u.is_valid());
1103 SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
1104 SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
1105 SaveFileCreateInfo::SAVE_FILE_FROM_NET;
1106 SaveItem* save_item = new SaveItem(u, referrers_list[i],
1107 this, save_source);
1108 waiting_item_queue_.push(save_item);
1109 }
1110 // Put all HTML resources to wait list.
1111 for (int i = 0; i < static_cast<int>(frames_list.size()); ++i) {
1112 const GURL& u = frames_list[i];
1113 DCHECK(u.is_valid());
1114 SaveItem* save_item = new SaveItem(u, GURL(),
1115 this, SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
1116 waiting_item_queue_.push(save_item);
1117 }
1118 wait_state_ = NET_FILES;
1119 DoSavingProcess();
1120 } else {
1121 // No resource files need to be saved, treat it as user cancel.
1122 Cancel(true);
1123 }
1124 }
1125
1126 void SavePackage::SetShouldPromptUser(bool should_prompt) {
1127 g_should_prompt_for_filename = should_prompt;
1128 }
1129
1130 FilePath SavePackage::GetSuggestedNameForSaveAs(
1131 bool can_save_as_complete,
1132 const std::string& contents_mime_type) {
1133 FilePath name_with_proper_ext =
1134 FilePath::FromWStringHack(UTF16ToWideHack(title_));
1135
1136 // If the page's title matches its URL, use the URL. Try to use the last path
1137 // component or if there is none, the domain as the file name.
1138 // Normally we want to base the filename on the page title, or if it doesn't
1139 // exist, on the URL. It's not easy to tell if the page has no title, because
1140 // if the page has no title, TabContents::GetTitle() will return the page's
1141 // URL (adjusted for display purposes). Therefore, we convert the "title"
1142 // back to a URL, and if it matches the original page URL, we know the page
1143 // had no title (or had a title equal to its URL, which is fine to treat
1144 // similarly).
1145 GURL fixed_up_title_url =
1146 URLFixerUpper::FixupURL(UTF16ToUTF8(title_), std::string());
1147
1148 if (page_url_ == fixed_up_title_url) {
1149 std::string url_path;
1150 std::vector<std::string> url_parts;
1151 base::SplitString(page_url_.path(), '/', &url_parts);
1152 if (!url_parts.empty()) {
1153 for (int i = static_cast<int>(url_parts.size()) - 1; i >= 0; --i) {
1154 url_path = url_parts[i];
1155 if (!url_path.empty())
1156 break;
1157 }
1158 }
1159 if (url_path.empty())
1160 url_path = page_url_.host();
1161 name_with_proper_ext = FilePath::FromWStringHack(UTF8ToWide(url_path));
1162 }
1163
1164 // Ask user for getting final saving name.
1165 name_with_proper_ext = EnsureMimeExtension(name_with_proper_ext,
1166 contents_mime_type);
1167 // Adjust extension for complete types.
1168 if (can_save_as_complete)
1169 name_with_proper_ext = EnsureHtmlExtension(name_with_proper_ext);
1170
1171 FilePath::StringType file_name = name_with_proper_ext.value();
1172 file_util::ReplaceIllegalCharactersInPath(&file_name, ' ');
1173 return FilePath(file_name);
1174 }
1175
1176 FilePath SavePackage::EnsureHtmlExtension(const FilePath& name) {
1177 // If the file name doesn't have an extension suitable for HTML files,
1178 // append one.
1179 FilePath::StringType ext = name.Extension();
1180 if (!ext.empty())
1181 ext.erase(ext.begin()); // Erase preceding '.'.
1182 std::string mime_type;
1183 if (!net::GetMimeTypeFromExtension(ext, &mime_type) ||
1184 !CanSaveAsComplete(mime_type)) {
1185 return FilePath(name.value() + FILE_PATH_LITERAL(".") +
1186 kDefaultHtmlExtension);
1187 }
1188 return name;
1189 }
1190
1191 FilePath SavePackage::EnsureMimeExtension(const FilePath& name,
1192 const std::string& contents_mime_type) {
1193 // Start extension at 1 to skip over period if non-empty.
1194 FilePath::StringType ext = name.Extension().length() ?
1195 name.Extension().substr(1) : name.Extension();
1196 FilePath::StringType suggested_extension =
1197 ExtensionForMimeType(contents_mime_type);
1198 std::string mime_type;
1199 if (!suggested_extension.empty() &&
1200 (!net::GetMimeTypeFromExtension(ext, &mime_type) ||
1201 !IsSavableContents(mime_type))) {
1202 // Extension is absent or needs to be updated.
1203 return FilePath(name.value() + FILE_PATH_LITERAL(".") +
1204 suggested_extension);
1205 }
1206 return name;
1207 }
1208
1209 const FilePath::CharType* SavePackage::ExtensionForMimeType(
1210 const std::string& contents_mime_type) {
1211 static const struct {
1212 const FilePath::CharType *mime_type;
1213 const FilePath::CharType *suggested_extension;
1214 } extensions[] = {
1215 { FILE_PATH_LITERAL("text/html"), kDefaultHtmlExtension },
1216 { FILE_PATH_LITERAL("text/xml"), FILE_PATH_LITERAL("xml") },
1217 { FILE_PATH_LITERAL("application/xhtml+xml"), FILE_PATH_LITERAL("xhtml") },
1218 { FILE_PATH_LITERAL("text/plain"), FILE_PATH_LITERAL("txt") },
1219 { FILE_PATH_LITERAL("text/css"), FILE_PATH_LITERAL("css") },
1220 };
1221 #if defined(OS_POSIX)
1222 FilePath::StringType mime_type(contents_mime_type);
1223 #elif defined(OS_WIN)
1224 FilePath::StringType mime_type(UTF8ToWide(contents_mime_type));
1225 #endif // OS_WIN
1226 for (uint32 i = 0; i < ARRAYSIZE_UNSAFE(extensions); ++i) {
1227 if (mime_type == extensions[i].mime_type)
1228 return extensions[i].suggested_extension;
1229 }
1230 return FILE_PATH_LITERAL("");
1231 }
1232
1233
1234
1235 // static.
1236 // Check whether the preference has the preferred directory for saving file. If
1237 // not, initialize it with default directory.
1238 FilePath SavePackage::GetSaveDirPreference(PrefService* prefs) {
1239 DCHECK(prefs);
1240
1241 if (!prefs->FindPreference(prefs::kSaveFileDefaultDirectory)) {
1242 DCHECK(prefs->FindPreference(prefs::kDownloadDefaultDirectory));
1243 FilePath default_save_path = prefs->GetFilePath(
1244 prefs::kDownloadDefaultDirectory);
1245 prefs->RegisterFilePathPref(prefs::kSaveFileDefaultDirectory,
1246 default_save_path,
1247 PrefService::UNSYNCABLE_PREF);
1248 }
1249
1250 // Get the directory from preference.
1251 FilePath save_file_path = prefs->GetFilePath(
1252 prefs::kSaveFileDefaultDirectory);
1253 DCHECK(!save_file_path.empty());
1254
1255 return save_file_path;
1256 }
1257
1258 void SavePackage::GetSaveInfo() {
1259 // Can't use tab_contents_ in the file thread, so get the data that we need
1260 // before calling to it.
1261 PrefService* prefs = tab_contents()->profile()->GetPrefs();
1262 FilePath website_save_dir = GetSaveDirPreference(prefs);
1263 FilePath download_save_dir = prefs->GetFilePath(
1264 prefs::kDownloadDefaultDirectory);
1265 std::string mime_type = tab_contents()->contents_mime_type();
1266
1267 BrowserThread::PostTask(
1268 BrowserThread::FILE, FROM_HERE,
1269 NewRunnableMethod(this, &SavePackage::CreateDirectoryOnFileThread,
1270 website_save_dir, download_save_dir, mime_type));
1271 }
1272
1273 void SavePackage::CreateDirectoryOnFileThread(
1274 const FilePath& website_save_dir,
1275 const FilePath& download_save_dir,
1276 const std::string& mime_type) {
1277 FilePath save_dir;
1278 // If the default html/websites save folder doesn't exist...
1279 if (!file_util::DirectoryExists(website_save_dir)) {
1280 // If the default download dir doesn't exist, create it.
1281 if (!file_util::DirectoryExists(download_save_dir))
1282 file_util::CreateDirectory(download_save_dir);
1283 save_dir = download_save_dir;
1284 } else {
1285 // If it does exist, use the default save dir param.
1286 save_dir = website_save_dir;
1287 }
1288
1289 bool can_save_as_complete = CanSaveAsComplete(mime_type);
1290 FilePath suggested_filename = GetSuggestedNameForSaveAs(can_save_as_complete,
1291 mime_type);
1292 FilePath::StringType pure_file_name =
1293 suggested_filename.RemoveExtension().BaseName().value();
1294 FilePath::StringType file_name_ext = suggested_filename.Extension();
1295
1296 // Need to make sure the suggested file name is not too long.
1297 uint32 max_path = GetMaxPathLengthForDirectory(save_dir);
1298
1299 if (GetSafePureFileName(save_dir, file_name_ext, max_path, &pure_file_name)) {
1300 save_dir = save_dir.Append(pure_file_name + file_name_ext);
1301 } else {
1302 // Cannot create a shorter filename. This will cause the save as operation
1303 // to fail unless the user pick a shorter name. Continuing even though it
1304 // will fail because returning means no save as popup for the user, which
1305 // is even more confusing. This case should be rare though.
1306 save_dir = save_dir.Append(suggested_filename);
1307 }
1308
1309 BrowserThread::PostTask(
1310 BrowserThread::UI, FROM_HERE,
1311 NewRunnableMethod(this, &SavePackage::ContinueGetSaveInfo, save_dir,
1312 can_save_as_complete));
1313 }
1314
1315 void SavePackage::ContinueGetSaveInfo(const FilePath& suggested_path,
1316 bool can_save_as_complete) {
1317 // The TabContents which owns this SavePackage may have disappeared during
1318 // the UI->FILE->UI thread hop of
1319 // GetSaveInfo->CreateDirectoryOnFileThread->ContinueGetSaveInfo.
1320 if (!tab_contents())
1321 return;
1322 DownloadPrefs* download_prefs =
1323 tab_contents()->profile()->GetDownloadManager()->download_prefs();
1324 int file_type_index =
1325 SavePackageTypeToIndex(
1326 static_cast<SavePackageType>(download_prefs->save_file_type()));
1327 DCHECK_NE(-1, file_type_index);
1328
1329 SelectFileDialog::FileTypeInfo file_type_info;
1330 FilePath::StringType default_extension;
1331
1332 // If the contents can not be saved as complete-HTML, do not show the
1333 // file filters.
1334 if (can_save_as_complete) {
1335 bool add_extra_extension = false;
1336 FilePath::StringType extra_extension;
1337 if (!suggested_path.Extension().empty() &&
1338 suggested_path.Extension().compare(FILE_PATH_LITERAL("htm")) &&
1339 suggested_path.Extension().compare(FILE_PATH_LITERAL("html"))) {
1340 add_extra_extension = true;
1341 extra_extension = suggested_path.Extension().substr(1);
1342 }
1343
1344 file_type_info.extensions.resize(2);
1345 file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1346 FILE_PATH_LITERAL("htm"));
1347 file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1348 FILE_PATH_LITERAL("html"));
1349
1350 if (add_extra_extension) {
1351 file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1352 extra_extension);
1353 }
1354
1355 file_type_info.extension_description_overrides.push_back(
1356 l10n_util::GetStringUTF16(kIndexToIDS[kSelectFileCompleteIndex - 1]));
1357 file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
1358 FILE_PATH_LITERAL("htm"));
1359 file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
1360 FILE_PATH_LITERAL("html"));
1361
1362 if (add_extra_extension) {
1363 file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
1364 extra_extension);
1365 }
1366
1367 file_type_info.extension_description_overrides.push_back(
1368 l10n_util::GetStringUTF16(kIndexToIDS[kSelectFileCompleteIndex]));
1369 file_type_info.include_all_files = false;
1370 default_extension = kDefaultHtmlExtension;
1371 } else {
1372 file_type_info.extensions.resize(1);
1373 file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1374 suggested_path.Extension());
1375
1376 if (!file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1][0].empty()) {
1377 // Drop the .
1378 file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1][0].erase(0, 1);
1379 }
1380
1381 file_type_info.include_all_files = true;
1382 file_type_index = 1;
1383 }
1384
1385 if (g_should_prompt_for_filename) {
1386 if (!select_file_dialog_.get())
1387 select_file_dialog_ = SelectFileDialog::Create(this);
1388 select_file_dialog_->SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE,
1389 string16(),
1390 suggested_path,
1391 &file_type_info,
1392 file_type_index,
1393 default_extension,
1394 tab_contents(),
1395 platform_util::GetTopLevel(
1396 tab_contents()->GetNativeView()),
1397 NULL);
1398 } else {
1399 // Just use 'suggested_path' instead of opening the dialog prompt.
1400 ContinueSave(suggested_path, file_type_index);
1401 }
1402 }
1403
1404 // Called after the save file dialog box returns.
1405 void SavePackage::ContinueSave(const FilePath& final_name,
1406 int index) {
1407 // Ensure the filename is safe.
1408 saved_main_file_path_ = final_name;
1409 download_util::GenerateSafeFileName(tab_contents()->contents_mime_type(),
1410 &saved_main_file_path_);
1411
1412 // The option index is not zero-based.
1413 DCHECK(index >= kSelectFileHtmlOnlyIndex &&
1414 index <= kSelectFileCompleteIndex);
1415
1416 saved_main_directory_path_ = saved_main_file_path_.DirName();
1417
1418 PrefService* prefs = tab_contents()->profile()->GetPrefs();
1419 StringPrefMember save_file_path;
1420 save_file_path.Init(prefs::kSaveFileDefaultDirectory, prefs, NULL);
1421 #if defined(OS_POSIX)
1422 std::string path_string = saved_main_directory_path_.value();
1423 #elif defined(OS_WIN)
1424 std::string path_string = WideToUTF8(saved_main_directory_path_.value());
1425 #endif
1426 // If user change the default saving directory, we will remember it just
1427 // like IE and FireFox.
1428 if (!tab_contents()->profile()->IsOffTheRecord() &&
1429 save_file_path.GetValue() != path_string) {
1430 save_file_path.SetValue(path_string);
1431 }
1432
1433 save_type_ = kIndexToSaveType[index];
1434
1435 prefs->SetInteger(prefs::kSaveFileType, save_type_);
1436
1437 if (save_type_ == SavePackage::SAVE_AS_COMPLETE_HTML) {
1438 // Make new directory for saving complete file.
1439 saved_main_directory_path_ = saved_main_directory_path_.Append(
1440 saved_main_file_path_.RemoveExtension().BaseName().value() +
1441 FILE_PATH_LITERAL("_files"));
1442 }
1443
1444 Init();
1445 }
1446
1447 // Static
1448 bool SavePackage::IsSavableURL(const GURL& url) {
1449 for (int i = 0; chrome::kSavableSchemes[i] != NULL; ++i) {
1450 if (url.SchemeIs(chrome::kSavableSchemes[i])) {
1451 return true;
1452 }
1453 }
1454 return false;
1455 }
1456
1457 // Static
1458 bool SavePackage::IsSavableContents(const std::string& contents_mime_type) {
1459 // WebKit creates Document object when MIME type is application/xhtml+xml,
1460 // so we also support this MIME type.
1461 return contents_mime_type == "text/html" ||
1462 contents_mime_type == "text/xml" ||
1463 contents_mime_type == "application/xhtml+xml" ||
1464 contents_mime_type == "text/plain" ||
1465 contents_mime_type == "text/css" ||
1466 net::IsSupportedJavascriptMimeType(contents_mime_type.c_str());
1467 }
1468
1469 // SelectFileDialog::Listener interface.
1470 void SavePackage::FileSelected(const FilePath& path,
1471 int index, void* params) {
1472 ContinueSave(path, index);
1473 }
1474
1475 void SavePackage::FileSelectionCanceled(void* params) {
1476 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698