Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / bookmarks / bookmark_html_writer.cc
blob8d4796c2b43b728a9e6b332b85a2e9e19715467c
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.
5 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
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"
32 using content::BrowserThread;
34 namespace {
36 static BookmarkFaviconFetcher* fetcher = NULL;
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";
50 // Newline separator.
51 const char kNewline[] = "\r\n";
53 // The following are used for bookmarks.
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>";
66 // The following are used when writing folders.
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>";
83 // Number of characters to indent by.
84 const size_t kIndentSize = 4;
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) {
99 // Writing bookmarks and favicons data to file.
100 void DoWrite() {
101 if (!OpenFile())
102 return;
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;
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.
132 IncrementIndent();
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;
143 DecrementIndent();
145 Write(kFolderChildrenEnd);
146 Write(kNewline);
147 // File close is forced so that unit test could read it.
148 file_.reset();
150 NotifyOnFinish();
153 private:
154 friend class base::RefCountedThreadSafe<Writer>;
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,
163 // Actual content, eg foo in <h1>foo</h2>.
164 CONTENT
167 ~Writer() {}
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();
176 // Increments the indent.
177 void IncrementIndent() {
178 indent_.resize(indent_.size() + kIndentSize, ' ');
181 // Decrements the indent.
182 void DecrementIndent() {
183 DCHECK(!indent_.empty());
184 indent_.resize(indent_.size() - kIndentSize, ' ');
187 // Called at the end of the export process.
188 void NotifyOnFinish() {
189 if (observer_ != NULL) {
190 observer_->OnExportFinished();
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;
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(IsStringUTF8(text));
209 std::string utf8_string;
211 switch (type) {
212 case ATTRIBUTE_VALUE:
213 // Convert " to &quot;
214 utf8_string = text;
215 ReplaceSubstringsAfterOffset(&utf8_string, 0, "\"", "&quot;");
216 break;
218 case CONTENT:
219 utf8_string = net::EscapeForHTML(text);
220 break;
222 default:
223 NOTREACHED();
226 return Write(utf8_string);
229 // Indents the current line.
230 bool WriteIndent() {
231 return Write(indent_);
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()));
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;
256 if (type_string == BookmarkCodec::kTypeURL) {
257 std::string url_string;
258 if (!value.GetString(BookmarkCodec::kURLKey, &url_string)) {
259 NOTREACHED();
260 return false;
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();
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;
289 return true;
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;
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;
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;
321 if (!Write(title, CONTENT) ||
322 !Write(kFolderEnd) ||
323 !Write(kNewline) ||
324 !WriteIndent() ||
325 !Write(kFolderChildren) ||
326 !Write(kNewline)) {
327 return false;
329 IncrementIndent();
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;
342 if (!WriteNode(*static_cast<const base::DictionaryValue*>(child_value),
343 BookmarkNode::FOLDER)) {
344 return false;
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;
357 return true;
360 // The BookmarkModel as a base::Value. This value was generated from the
361 // BookmarkCodec.
362 scoped_ptr<base::Value> bookmarks_;
364 // Path we're writing to.
365 base::FilePath path_;
367 // Map that stores favicon per URL.
368 scoped_ptr<BookmarkFaviconFetcher::URLFaviconMap> favicons_map_;
370 // Observer to be notified on finish.
371 BookmarksExportObserver* observer_;
373 // File we're writing to.
374 scoped_ptr<base::File> file_;
376 // How much we indent when writing a bookmark/folder. This is modified
377 // via IncrementIndent and DecrementIndent.
378 std::string indent_;
380 DISALLOW_COPY_AND_ASSIGN(Writer);
383 } // namespace
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_));
398 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
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();
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;
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));
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;
450 bool BookmarkFaviconFetcher::FetchNextFavicon() {
451 if (bookmark_urls_.empty()) {
452 return false;
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();
472 } while (!bookmark_urls_.empty());
473 return false;
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();
483 if (bitmap_result.is_valid() && !url.is_empty()) {
484 favicons_map_->insert(
485 make_pair(url.spec(), bitmap_result.bitmap_data));
488 if (FetchNextFavicon()) {
489 return;
491 ExecuteWriter();
494 namespace bookmark_html_writer {
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();
508 } // namespace bookmark_html_writer