Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / bookmarks / bookmark_html_writer.cc
1 // Copyright (c) 2012 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/bookmarks/bookmark_html_writer.h"
6
7 #include "base/base64.h"
8 #include "base/bind.h"
9 #include "base/bind_helpers.h"
10 #include "base/callback.h"
11 #include "base/files/file.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/platform_file.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
19 #include "chrome/browser/chrome_notification_types.h"
20 #include "chrome/browser/favicon/favicon_service.h"
21 #include "chrome/browser/favicon/favicon_service_factory.h"
22 #include "components/bookmarks/core/browser/bookmark_codec.h"
23 #include "components/bookmarks/core/browser/bookmark_model.h"
24 #include "components/favicon_base/favicon_types.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/notification_source.h"
27 #include "grit/component_strings.h"
28 #include "net/base/escape.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/gfx/favicon_size.h"
31
32 using content::BrowserThread;
33
34 namespace {
35
36 static BookmarkFaviconFetcher* fetcher = NULL;
37
38 // File header.
39 const char kHeader[] =
40     "<!DOCTYPE NETSCAPE-Bookmark-file-1>\r\n"
41     "<!-- This is an automatically generated file.\r\n"
42     "     It will be read and overwritten.\r\n"
43     "     DO NOT EDIT! -->\r\n"
44     "<META HTTP-EQUIV=\"Content-Type\""
45     " CONTENT=\"text/html; charset=UTF-8\">\r\n"
46     "<TITLE>Bookmarks</TITLE>\r\n"
47     "<H1>Bookmarks</H1>\r\n"
48     "<DL><p>\r\n";
49
50 // Newline separator.
51 const char kNewline[] = "\r\n";
52
53 // The following are used for bookmarks.
54
55 // Start of a bookmark.
56 const char kBookmarkStart[] = "<DT><A HREF=\"";
57 // After kBookmarkStart.
58 const char kAddDate[] = "\" ADD_DATE=\"";
59 // After kAddDate.
60 const char kIcon[] = "\" ICON=\"";
61 // After kIcon.
62 const char kBookmarkAttributeEnd[] = "\">";
63 // End of a bookmark.
64 const char kBookmarkEnd[] = "</A>";
65
66 // The following are used when writing folders.
67
68 // Start of a folder.
69 const char kFolderStart[] = "<DT><H3 ADD_DATE=\"";
70 // After kFolderStart.
71 const char kLastModified[] = "\" LAST_MODIFIED=\"";
72 // After kLastModified when writing the bookmark bar.
73 const char kBookmarkBar[] = "\" PERSONAL_TOOLBAR_FOLDER=\"true\">";
74 // After kLastModified when writing a user created folder.
75 const char kFolderAttributeEnd[] = "\">";
76 // End of the folder.
77 const char kFolderEnd[] = "</H3>";
78 // Start of the children of a folder.
79 const char kFolderChildren[] = "<DL><p>";
80 // End of the children for a folder.
81 const char kFolderChildrenEnd[] = "</DL><p>";
82
83 // Number of characters to indent by.
84 const size_t kIndentSize = 4;
85
86 // Class responsible for the actual writing. Takes ownership of favicons_map.
87 class Writer : public base::RefCountedThreadSafe<Writer> {
88  public:
89   Writer(base::Value* bookmarks,
90          const base::FilePath& path,
91          BookmarkFaviconFetcher::URLFaviconMap* favicons_map,
92          BookmarksExportObserver* observer)
93       : bookmarks_(bookmarks),
94         path_(path),
95         favicons_map_(favicons_map),
96         observer_(observer) {
97   }
98
99   // Writing bookmarks and favicons data to file.
100   void DoWrite() {
101     if (!OpenFile())
102       return;
103
104     base::Value* roots = NULL;
105     if (!Write(kHeader) ||
106         bookmarks_->GetType() != base::Value::TYPE_DICTIONARY ||
107         !static_cast<base::DictionaryValue*>(bookmarks_.get())->Get(
108             BookmarkCodec::kRootsKey, &roots) ||
109         roots->GetType() != base::Value::TYPE_DICTIONARY) {
110       NOTREACHED();
111       return;
112     }
113
114     base::DictionaryValue* roots_d_value =
115         static_cast<base::DictionaryValue*>(roots);
116     base::Value* root_folder_value;
117     base::Value* other_folder_value = NULL;
118     base::Value* mobile_folder_value = NULL;
119     if (!roots_d_value->Get(BookmarkCodec::kRootFolderNameKey,
120                             &root_folder_value) ||
121         root_folder_value->GetType() != base::Value::TYPE_DICTIONARY ||
122         !roots_d_value->Get(BookmarkCodec::kOtherBookmarkFolderNameKey,
123                             &other_folder_value) ||
124         other_folder_value->GetType() != base::Value::TYPE_DICTIONARY ||
125         !roots_d_value->Get(BookmarkCodec::kMobileBookmarkFolderNameKey,
126                             &mobile_folder_value) ||
127         mobile_folder_value->GetType() != base::Value::TYPE_DICTIONARY) {
128       NOTREACHED();
129       return;  // Invalid type for root folder and/or other folder.
130     }
131
132     IncrementIndent();
133
134     if (!WriteNode(*static_cast<base::DictionaryValue*>(root_folder_value),
135                    BookmarkNode::BOOKMARK_BAR) ||
136         !WriteNode(*static_cast<base::DictionaryValue*>(other_folder_value),
137                    BookmarkNode::OTHER_NODE) ||
138         !WriteNode(*static_cast<base::DictionaryValue*>(mobile_folder_value),
139                    BookmarkNode::MOBILE)) {
140       return;
141     }
142
143     DecrementIndent();
144
145     Write(kFolderChildrenEnd);
146     Write(kNewline);
147     // File close is forced so that unit test could read it.
148     file_.reset();
149
150     NotifyOnFinish();
151   }
152
153  private:
154   friend class base::RefCountedThreadSafe<Writer>;
155
156   // Types of text being written out. The type dictates how the text is
157   // escaped.
158   enum TextType {
159     // The text is the value of an html attribute, eg foo in
160     // <a href="foo">.
161     ATTRIBUTE_VALUE,
162
163     // Actual content, eg foo in <h1>foo</h2>.
164     CONTENT
165   };
166
167   ~Writer() {}
168
169   // Opens the file, returning true on success.
170   bool OpenFile() {
171     int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
172     file_.reset(new base::File(path_, flags));
173     return file_->IsValid();
174   }
175
176   // Increments the indent.
177   void IncrementIndent() {
178     indent_.resize(indent_.size() + kIndentSize, ' ');
179   }
180
181   // Decrements the indent.
182   void DecrementIndent() {
183     DCHECK(!indent_.empty());
184     indent_.resize(indent_.size() - kIndentSize, ' ');
185   }
186
187   // Called at the end of the export process.
188   void NotifyOnFinish() {
189     if (observer_ != NULL) {
190       observer_->OnExportFinished();
191     }
192   }
193
194   // Writes raw text out returning true on success. This does not escape
195   // the text in anyway.
196   bool Write(const std::string& text) {
197     if (!text.length())
198       return true;
199     size_t wrote = file_->WriteAtCurrentPos(text.c_str(), text.length());
200     bool result = (wrote == text.length());
201     DCHECK(result);
202     return result;
203   }
204
205   // Writes out the text string (as UTF8). The text is escaped based on
206   // type.
207   bool Write(const std::string& text, TextType type) {
208     DCHECK(base::IsStringUTF8(text));
209     std::string utf8_string;
210
211     switch (type) {
212       case ATTRIBUTE_VALUE:
213         // Convert " to &quot;
214         utf8_string = text;
215         ReplaceSubstringsAfterOffset(&utf8_string, 0, "\"", "&quot;");
216         break;
217
218       case CONTENT:
219         utf8_string = net::EscapeForHTML(text);
220         break;
221
222       default:
223         NOTREACHED();
224     }
225
226     return Write(utf8_string);
227   }
228
229   // Indents the current line.
230   bool WriteIndent() {
231     return Write(indent_);
232   }
233
234   // Converts a time string written to the JSON codec into a time_t string
235   // (used by bookmarks.html) and writes it.
236   bool WriteTime(const std::string& time_string) {
237     int64 internal_value;
238     base::StringToInt64(time_string, &internal_value);
239     return Write(base::Int64ToString(
240         base::Time::FromInternalValue(internal_value).ToTimeT()));
241   }
242
243   // Writes the node and all its children, returning true on success.
244   bool WriteNode(const base::DictionaryValue& value,
245                 BookmarkNode::Type folder_type) {
246     std::string title, date_added_string, type_string;
247     if (!value.GetString(BookmarkCodec::kNameKey, &title) ||
248         !value.GetString(BookmarkCodec::kDateAddedKey, &date_added_string) ||
249         !value.GetString(BookmarkCodec::kTypeKey, &type_string) ||
250         (type_string != BookmarkCodec::kTypeURL &&
251          type_string != BookmarkCodec::kTypeFolder))  {
252       NOTREACHED();
253       return false;
254     }
255
256     if (type_string == BookmarkCodec::kTypeURL) {
257       std::string url_string;
258       if (!value.GetString(BookmarkCodec::kURLKey, &url_string)) {
259         NOTREACHED();
260         return false;
261       }
262
263       std::string favicon_string;
264       BookmarkFaviconFetcher::URLFaviconMap::iterator itr =
265           favicons_map_->find(url_string);
266       if (itr != favicons_map_->end()) {
267         scoped_refptr<base::RefCountedMemory> data(itr->second.get());
268         std::string favicon_base64_encoded;
269         base::Base64Encode(std::string(data->front_as<char>(), data->size()),
270                            &favicon_base64_encoded);
271         GURL favicon_url("data:image/png;base64," + favicon_base64_encoded);
272         favicon_string = favicon_url.spec();
273       }
274
275       if (!WriteIndent() ||
276           !Write(kBookmarkStart) ||
277           !Write(url_string, ATTRIBUTE_VALUE) ||
278           !Write(kAddDate) ||
279           !WriteTime(date_added_string) ||
280           (!favicon_string.empty() &&
281               (!Write(kIcon) ||
282                !Write(favicon_string, ATTRIBUTE_VALUE))) ||
283           !Write(kBookmarkAttributeEnd) ||
284           !Write(title, CONTENT) ||
285           !Write(kBookmarkEnd) ||
286           !Write(kNewline)) {
287         return false;
288       }
289       return true;
290     }
291
292     // Folder.
293     std::string last_modified_date;
294     const base::Value* child_values = NULL;
295     if (!value.GetString(BookmarkCodec::kDateModifiedKey,
296                          &last_modified_date) ||
297         !value.Get(BookmarkCodec::kChildrenKey, &child_values) ||
298         child_values->GetType() != base::Value::TYPE_LIST) {
299       NOTREACHED();
300       return false;
301     }
302     if (folder_type != BookmarkNode::OTHER_NODE &&
303         folder_type != BookmarkNode::MOBILE) {
304       // The other/mobile folder name are not written out. This gives the effect
305       // of making the contents of the 'other folder' be a sibling to the
306       // bookmark bar folder.
307       if (!WriteIndent() ||
308           !Write(kFolderStart) ||
309           !WriteTime(date_added_string) ||
310           !Write(kLastModified) ||
311           !WriteTime(last_modified_date)) {
312         return false;
313       }
314       if (folder_type == BookmarkNode::BOOKMARK_BAR) {
315         if (!Write(kBookmarkBar))
316           return false;
317         title = l10n_util::GetStringUTF8(IDS_BOOKMARK_BAR_FOLDER_NAME);
318       } else if (!Write(kFolderAttributeEnd)) {
319         return false;
320       }
321       if (!Write(title, CONTENT) ||
322           !Write(kFolderEnd) ||
323           !Write(kNewline) ||
324           !WriteIndent() ||
325           !Write(kFolderChildren) ||
326           !Write(kNewline)) {
327         return false;
328       }
329       IncrementIndent();
330     }
331
332     // Write the children.
333     const base::ListValue* children =
334         static_cast<const base::ListValue*>(child_values);
335     for (size_t i = 0; i < children->GetSize(); ++i) {
336       const base::Value* child_value;
337       if (!children->Get(i, &child_value) ||
338           child_value->GetType() != base::Value::TYPE_DICTIONARY) {
339         NOTREACHED();
340         return false;
341       }
342       if (!WriteNode(*static_cast<const base::DictionaryValue*>(child_value),
343                      BookmarkNode::FOLDER)) {
344         return false;
345       }
346     }
347     if (folder_type != BookmarkNode::OTHER_NODE &&
348         folder_type != BookmarkNode::MOBILE) {
349       // Close out the folder.
350       DecrementIndent();
351       if (!WriteIndent() ||
352           !Write(kFolderChildrenEnd) ||
353           !Write(kNewline)) {
354         return false;
355       }
356     }
357     return true;
358   }
359
360   // The BookmarkModel as a base::Value. This value was generated from the
361   // BookmarkCodec.
362   scoped_ptr<base::Value> bookmarks_;
363
364   // Path we're writing to.
365   base::FilePath path_;
366
367   // Map that stores favicon per URL.
368   scoped_ptr<BookmarkFaviconFetcher::URLFaviconMap> favicons_map_;
369
370   // Observer to be notified on finish.
371   BookmarksExportObserver* observer_;
372
373   // File we're writing to.
374   scoped_ptr<base::File> file_;
375
376   // How much we indent when writing a bookmark/folder. This is modified
377   // via IncrementIndent and DecrementIndent.
378   std::string indent_;
379
380   DISALLOW_COPY_AND_ASSIGN(Writer);
381 };
382
383 }  // namespace
384
385 BookmarkFaviconFetcher::BookmarkFaviconFetcher(
386     Profile* profile,
387     const base::FilePath& path,
388     BookmarksExportObserver* observer)
389     : profile_(profile),
390       path_(path),
391       observer_(observer) {
392   favicons_map_.reset(new URLFaviconMap());
393   registrar_.Add(this,
394                  chrome::NOTIFICATION_PROFILE_DESTROYED,
395                  content::Source<Profile>(profile_));
396 }
397
398 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
399 }
400
401 void BookmarkFaviconFetcher::ExportBookmarks() {
402   ExtractUrls(BookmarkModelFactory::GetForProfile(
403       profile_)->bookmark_bar_node());
404   ExtractUrls(BookmarkModelFactory::GetForProfile(profile_)->other_node());
405   ExtractUrls(BookmarkModelFactory::GetForProfile(profile_)->mobile_node());
406   if (!bookmark_urls_.empty())
407     FetchNextFavicon();
408   else
409     ExecuteWriter();
410 }
411
412 void BookmarkFaviconFetcher::Observe(
413     int type,
414     const content::NotificationSource& source,
415     const content::NotificationDetails& details) {
416   if (chrome::NOTIFICATION_PROFILE_DESTROYED == type && fetcher != NULL) {
417     base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
418     fetcher = NULL;
419   }
420 }
421
422 void BookmarkFaviconFetcher::ExtractUrls(const BookmarkNode* node) {
423   if (node->is_url()) {
424     std::string url = node->url().spec();
425     if (!url.empty())
426       bookmark_urls_.push_back(url);
427   } else {
428     for (int i = 0; i < node->child_count(); ++i)
429       ExtractUrls(node->GetChild(i));
430   }
431 }
432
433 void BookmarkFaviconFetcher::ExecuteWriter() {
434   // BookmarkModel isn't thread safe (nor would we want to lock it down
435   // for the duration of the write), as such we make a copy of the
436   // BookmarkModel using BookmarkCodec then write from that.
437   BookmarkCodec codec;
438   BrowserThread::PostTask(
439       BrowserThread::FILE, FROM_HERE,
440       base::Bind(&Writer::DoWrite,
441                  new Writer(codec.Encode(BookmarkModelFactory::GetForProfile(
442                                 profile_)),
443                             path_, favicons_map_.release(), observer_)));
444   if (fetcher != NULL) {
445     base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
446     fetcher = NULL;
447   }
448 }
449
450 bool BookmarkFaviconFetcher::FetchNextFavicon() {
451   if (bookmark_urls_.empty()) {
452     return false;
453   }
454   do {
455     std::string url = bookmark_urls_.front();
456     // Filter out urls that we've already got favicon for.
457     URLFaviconMap::const_iterator iter = favicons_map_->find(url);
458     if (favicons_map_->end() == iter) {
459       FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
460           profile_, Profile::EXPLICIT_ACCESS);
461       favicon_service->GetRawFaviconForURL(
462           FaviconService::FaviconForURLParams(
463               GURL(url), favicon_base::FAVICON, gfx::kFaviconSize),
464           ui::SCALE_FACTOR_100P,
465           base::Bind(&BookmarkFaviconFetcher::OnFaviconDataAvailable,
466                      base::Unretained(this)),
467           &cancelable_task_tracker_);
468       return true;
469     } else {
470       bookmark_urls_.pop_front();
471     }
472   } while (!bookmark_urls_.empty());
473   return false;
474 }
475
476 void BookmarkFaviconFetcher::OnFaviconDataAvailable(
477     const favicon_base::FaviconBitmapResult& bitmap_result) {
478   GURL url;
479   if (!bookmark_urls_.empty()) {
480     url = GURL(bookmark_urls_.front());
481     bookmark_urls_.pop_front();
482   }
483   if (bitmap_result.is_valid() && !url.is_empty()) {
484     favicons_map_->insert(
485         make_pair(url.spec(), bitmap_result.bitmap_data));
486   }
487
488   if (FetchNextFavicon()) {
489     return;
490   }
491   ExecuteWriter();
492 }
493
494 namespace bookmark_html_writer {
495
496 void WriteBookmarks(Profile* profile,
497                     const base::FilePath& path,
498                     BookmarksExportObserver* observer) {
499   // BookmarkModel isn't thread safe (nor would we want to lock it down
500   // for the duration of the write), as such we make a copy of the
501   // BookmarkModel using BookmarkCodec then write from that.
502   if (fetcher == NULL) {
503     fetcher = new BookmarkFaviconFetcher(profile, path, observer);
504     fetcher->ExportBookmarks();
505   }
506 }
507
508 }  // namespace bookmark_html_writer