summaryrefslogtreecommitdiffstats
path: root/chrome/browser/bookmarks
diff options
context:
space:
mode:
authornkostylev@google.com <nkostylev@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-08 16:02:56 +0000
committernkostylev@google.com <nkostylev@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-08 16:02:56 +0000
commitd2879af4320bd17bd9082bf724f5222f6060f3d0 (patch)
treea3a9a853909b4bb332d22ab4db0e01ff5a6e28c0 /chrome/browser/bookmarks
parent1e6e3c99f8ca2ef9d8e86ddddec20dadc0fbf467 (diff)
downloadchromium_src-d2879af4320bd17bd9082bf724f5222f6060f3d0.zip
chromium_src-d2879af4320bd17bd9082bf724f5222f6060f3d0.tar.gz
chromium_src-d2879af4320bd17bd9082bf724f5222f6060f3d0.tar.bz2
Export bookmark favicon (base64 encoded png).
Code for importing icons from bookmarks HTML file is already in place. BUG=11362 TEST=Export bookmarks to HTML file, delete all existing bookmarks, import file. Review URL: http://codereview.chromium.org/543202 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38362 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/bookmarks')
-rw-r--r--chrome/browser/bookmarks/bookmark_html_writer.cc184
-rw-r--r--chrome/browser/bookmarks/bookmark_html_writer.h99
-rw-r--r--chrome/browser/bookmarks/bookmark_html_writer_unittest.cc120
3 files changed, 366 insertions, 37 deletions
diff --git a/chrome/browser/bookmarks/bookmark_html_writer.cc b/chrome/browser/bookmarks/bookmark_html_writer.cc
index a80abb1..f845b8a 100644
--- a/chrome/browser/bookmarks/bookmark_html_writer.cc
+++ b/chrome/browser/bookmarks/bookmark_html_writer.cc
@@ -1,30 +1,32 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/bookmarks/bookmark_html_writer.h"
#include "app/l10n_util.h"
+#include "base/base64.h"
#include "base/file_path.h"
#include "base/message_loop.h"
#include "base/platform_file.h"
#include "base/scoped_ptr.h"
#include "base/string_util.h"
#include "base/time.h"
-#include "base/values.h"
#include "chrome/browser/bookmarks/bookmark_codec.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/notification_service.h"
#include "grit/generated_resources.h"
#include "net/base/escape.h"
#include "net/base/file_stream.h"
#include "net/base/net_errors.h"
-namespace bookmark_html_writer {
-
namespace {
+static BookmarkFaviconFetcher* fetcher = NULL;
+
// File header.
const char kHeader[] =
"<!DOCTYPE NETSCAPE-Bookmark-file-1>\r\n"
@@ -47,6 +49,8 @@ const char kBookmarkStart[] = "<DT><A HREF=\"";
// After kBookmarkStart.
const char kAddDate[] = "\" ADD_DATE=\"";
// After kAddDate.
+const char kIcon[] = "\" ICON=\"";
+// After kIcon.
const char kBookmarkAttributeEnd[] = "\">";
// End of a bookmark.
const char kBookmarkEnd[] = "</A>";
@@ -71,17 +75,29 @@ const char kFolderChildrenEnd[] = "</DL><p>";
// Number of characters to indent by.
const size_t kIndentSize = 4;
-// Class responsible for the actual writing.
+// Class responsible for the actual writing. Takes ownership of favicons_map.
class Writer : public Task {
public:
- Writer(Value* bookmarks, const FilePath& path)
+ Writer(Value* bookmarks,
+ const FilePath& path,
+ BookmarkFaviconFetcher::URLFaviconMap* favicons_map,
+ BookmarksExportObserver* observer)
: bookmarks_(bookmarks),
- path_(path) {
+ path_(path),
+ favicons_map_(favicons_map),
+ observer_(observer) {
}
virtual void Run() {
- if (!OpenFile())
+ RunImpl();
+ NotifyOnFinish();
+ }
+
+ // Writing bookmarks and favicons data to file.
+ virtual void RunImpl() {
+ if (!OpenFile()) {
return;
+ }
Value* roots;
if (!Write(kHeader) ||
@@ -119,6 +135,8 @@ class Writer : public Task {
Write(kFolderChildrenEnd);
Write(kNewline);
+ // File stream close is forced so that unit test could read it.
+ file_stream_.Close();
}
private:
@@ -150,6 +168,13 @@ class Writer : public Task {
indent_.resize(indent_.size() - kIndentSize, ' ');
}
+ // Called at the end of the export process.
+ void NotifyOnFinish() {
+ if (observer_ != NULL) {
+ observer_->OnExportFinished();
+ }
+ }
+
// Writes raw text out returning true on success. This does not escape
// the text in anyway.
bool Write(const std::string& text) {
@@ -215,11 +240,30 @@ class Writer : public Task {
NOTREACHED();
return false;
}
+
+ std::string favicon_string;
+ BookmarkFaviconFetcher::URLFaviconMap::iterator itr =
+ favicons_map_->find(url_string);
+ if (itr != favicons_map_->end()) {
+ scoped_refptr<RefCountedBytes> data = itr->second.get();
+ std::string favicon_data;
+ favicon_data.assign(reinterpret_cast<char*>(&data->data.front()),
+ data->data.size());
+ std::string favicon_base64_encoded;
+ if (base::Base64Encode(favicon_data, &favicon_base64_encoded)) {
+ GURL favicon_url("data:image/png;base64," + favicon_base64_encoded);
+ favicon_string = favicon_url.spec();
+ }
+ }
+
if (!WriteIndent() ||
!Write(kBookmarkStart) ||
!Write(url_string, ATTRIBUTE_VALUE) ||
!Write(kAddDate) ||
!WriteTime(date_added_string) ||
+ (!favicon_string.empty() &&
+ (!Write(kIcon) ||
+ !Write(favicon_string, ATTRIBUTE_VALUE))) ||
!Write(kBookmarkAttributeEnd) ||
!Write(title, CONTENT) ||
!Write(kBookmarkEnd) ||
@@ -301,6 +345,12 @@ class Writer : public Task {
// Path we're writing to.
FilePath path_;
+ // Map that stores favicon per URL.
+ scoped_ptr<BookmarkFaviconFetcher::URLFaviconMap> favicons_map_;
+
+ // Observer to be notified on finish.
+ BookmarksExportObserver* observer_;
+
// File we're writing to.
net::FileStream file_stream_;
@@ -311,13 +361,125 @@ class Writer : public Task {
} // namespace
-void WriteBookmarks(BookmarkModel* model, const FilePath& path) {
+BookmarkFaviconFetcher::BookmarkFaviconFetcher(
+ Profile* profile,
+ const FilePath& path,
+ BookmarksExportObserver* observer)
+ : profile_(profile),
+ path_(path),
+ observer_(observer) {
+ favicons_map_.reset(new URLFaviconMap());
+ registrar_.Add(this,
+ NotificationType::PROFILE_DESTROYED,
+ NotificationService::AllSources());
+}
+
+BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
+}
+
+void BookmarkFaviconFetcher::ExportBookmarks() {
+ ExtractUrls(profile_->GetBookmarkModel()->GetBookmarkBarNode());
+ ExtractUrls(profile_->GetBookmarkModel()->other_node());
+ if (!bookmark_urls_.empty()) {
+ FetchNextFavicon();
+ } else {
+ ExecuteWriter();
+ }
+}
+
+void BookmarkFaviconFetcher::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (NotificationType::PROFILE_DESTROYED == type && fetcher != NULL) {
+ MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
+ fetcher = NULL;
+ }
+}
+
+void BookmarkFaviconFetcher::ExtractUrls(const BookmarkNode* node) {
+ if (BookmarkNode::URL == node->type()) {
+ std::string url = node->GetURL().spec();
+ if (!url.empty()) {
+ bookmark_urls_.push_back(url);
+ }
+ } else {
+ for (int i = 0; i < node->GetChildCount(); ++i) {
+ ExtractUrls(node->GetChild(i));
+ }
+ }
+}
+
+void BookmarkFaviconFetcher::ExecuteWriter() {
// BookmarkModel isn't thread safe (nor would we want to lock it down
// for the duration of the write), as such we make a copy of the
// BookmarkModel using BookmarkCodec then write from that.
BookmarkCodec codec;
- ChromeThread::PostTask(
- ChromeThread::FILE, FROM_HERE, new Writer(codec.Encode(model), path));
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ new Writer(codec.Encode(profile_->GetBookmarkModel()),
+ path_,
+ favicons_map_.release(),
+ observer_));
+ if (fetcher != NULL) {
+ MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
+ fetcher = NULL;
+ }
+}
+
+bool BookmarkFaviconFetcher::FetchNextFavicon() {
+ if (bookmark_urls_.empty()) {
+ return false;
+ }
+ do {
+ std::string url = bookmark_urls_.front();
+ // Filter out urls that we've already got favicon for.
+ URLFaviconMap::const_iterator iter = favicons_map_->find(url);
+ if (favicons_map_->end() == iter) {
+ FaviconService* favicon_service =
+ profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
+ favicon_service->GetFaviconForURL(GURL(url), &fav_icon_consumer_,
+ NewCallback(this, &BookmarkFaviconFetcher::OnFavIconDataAvailable));
+ return true;
+ } else {
+ bookmark_urls_.pop_front();
+ }
+ } while (!bookmark_urls_.empty());
+ return false;
+}
+
+void BookmarkFaviconFetcher::OnFavIconDataAvailable(
+ FaviconService::Handle handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedBytes> data,
+ bool expired,
+ GURL icon_url) {
+ GURL url;
+ if (!bookmark_urls_.empty()) {
+ url = GURL(bookmark_urls_.front());
+ bookmark_urls_.pop_front();
+ }
+ if (know_favicon && data.get() && !data->data.empty() && !url.is_empty()) {
+ favicons_map_->insert(make_pair(url.spec(), data));
+ }
+
+ if (FetchNextFavicon()) {
+ return;
+ }
+ ExecuteWriter();
+}
+
+namespace bookmark_html_writer {
+
+void WriteBookmarks(Profile* profile,
+ const FilePath& path,
+ BookmarksExportObserver* observer) {
+ // BookmarkModel isn't thread safe (nor would we want to lock it down
+ // for the duration of the write), as such we make a copy of the
+ // BookmarkModel using BookmarkCodec then write from that.
+ if (fetcher == NULL) {
+ fetcher = new BookmarkFaviconFetcher(profile, path, observer);
+ fetcher->ExportBookmarks();
+ }
}
} // namespace bookmark_html_writer
+
diff --git a/chrome/browser/bookmarks/bookmark_html_writer.h b/chrome/browser/bookmarks/bookmark_html_writer.h
index e7202de..0eba67f 100644
--- a/chrome/browser/bookmarks/bookmark_html_writer.h
+++ b/chrome/browser/bookmarks/bookmark_html_writer.h
@@ -1,23 +1,112 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_BOOKMARKS_BOOKMARK_HTML_WRITER_H_
#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_HTML_WRITER_H_
+#include <list>
+#include <map>
+#include <string>
+
+#include "base/ref_counted.h"
+#include "base/values.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/common/notification_registrar.h"
+#include "net/base/file_stream.h"
+
class BookmarkModel;
+class BookmarkNode;
+class DictionaryValue;
class FilePath;
-class MessageLoop;
+class GURL;
+class Profile;
+
+// Observer for bookmark html output. Used only in tests.
+class BookmarksExportObserver {
+ public:
+ // Is invoked on the IO thread.
+ virtual void OnExportFinished() = 0;
+
+ protected:
+ virtual ~BookmarksExportObserver() {}
+};
+
+// Class that fetches favicons for list of bookmarks and
+// then starts Writer which outputs bookmarks and favicons to html file.
+// Should be used only by WriteBookmarks function.
+class BookmarkFaviconFetcher: public NotificationObserver {
+ public:
+ // Map of URL and corresponding favicons.
+ typedef std::map<std::string, scoped_refptr<RefCountedBytes> > URLFaviconMap;
+
+ BookmarkFaviconFetcher(Profile* profile,
+ const FilePath& path,
+ BookmarksExportObserver* observer);
+ ~BookmarkFaviconFetcher();
+
+ // Executes bookmark export process.
+ void ExportBookmarks();
+
+ // NotificationObserver implementation.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ private:
+ // Recursively extracts URLs from bookmarks.
+ void ExtractUrls(const BookmarkNode* node);
+
+ // Executes Writer task that writes bookmarks data to html file.
+ void ExecuteWriter();
+
+ // Starts async fetch for the next bookmark favicon.
+ // Takes single url from bookmark_urls_ and removes it from the list.
+ // Returns true if there are more favicons to extract.
+ bool FetchNextFavicon();
+
+ // Favicon fetch callback. After all favicons are fetched executes
+ // html output on the file thread.
+ void OnFavIconDataAvailable(FaviconService::Handle handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedBytes> data,
+ bool expired,
+ GURL icon_url);
+
+ // The Profile object used for accessing FaviconService, bookmarks model.
+ Profile* profile_;
+
+ // All URLs that are extracted from bookmarks. Used to fetch favicons
+ // for each of them. After favicon is fetched top url is removed from list.
+ std::list<std::string> bookmark_urls_;
+
+ // Consumer for requesting favicons.
+ CancelableRequestConsumer fav_icon_consumer_;
+
+ // Map that stores favicon per URL.
+ scoped_ptr<URLFaviconMap> favicons_map_;
+
+ // Path where html output is stored.
+ FilePath path_;
+
+ BookmarksExportObserver* observer_;
+
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkFaviconFetcher);
+};
namespace bookmark_html_writer {
// Writes the bookmarks out in the 'bookmarks.html' format understood by
// Firefox and IE. The results are written to the file at |path|. The file
// thread is used.
-//
+// Before writing to the file favicons are fetched on the main thread.
// TODO(sky): need a callback on failure.
-void WriteBookmarks(BookmarkModel* model, const FilePath& path);
+void WriteBookmarks(Profile* profile,
+ const FilePath& path,
+ BookmarksExportObserver* observer);
-}
+} // namespace bookmark_html_writer
#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_HTML_WRITER_H_
diff --git a/chrome/browser/bookmarks/bookmark_html_writer_unittest.cc b/chrome/browser/bookmarks/bookmark_html_writer_unittest.cc
index 7d3b772..8f1d2fc 100644
--- a/chrome/browser/bookmarks/bookmark_html_writer_unittest.cc
+++ b/chrome/browser/bookmarks/bookmark_html_writer_unittest.cc
@@ -1,10 +1,11 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "testing/gtest/include/gtest/gtest.h"
#include "app/l10n_util.h"
+#include "app/gfx/codec/png_codec.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
@@ -15,7 +16,26 @@
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/importer/firefox2_importer.h"
+#include "chrome/test/testing_profile.h"
#include "grit/generated_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace {
+
+static const int kIconWidth = 16;
+static const int kIconHeight = 16;
+
+void MakeTestSkBitmap(int w, int h, SkBitmap* bmp) {
+ bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ bmp->allocPixels();
+
+ uint32_t* src_data = bmp->getAddr32(0, 0);
+ for (int i = 0; i < w * h; i++) {
+ src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240);
+ }
+}
+
+} // namespace
class BookmarkHTMLWriterTest : public testing::Test {
protected:
@@ -100,9 +120,43 @@ class BookmarkHTMLWriterTest : public testing::Test {
FilePath path_;
};
+// Class that will notify message loop when file is written.
+class BookmarksObserver : public BookmarksExportObserver {
+ public:
+ explicit BookmarksObserver(MessageLoop* loop) : loop_(loop) {
+ DCHECK(loop);
+ }
+
+ virtual void OnExportFinished() {
+ loop_->Quit();
+ }
+
+ private:
+ MessageLoop* loop_;
+ DISALLOW_COPY_AND_ASSIGN(BookmarksObserver);
+};
+
// Tests bookmark_html_writer by populating a BookmarkModel, writing it out by
// way of bookmark_html_writer, then using the importer to read it back in.
TEST_F(BookmarkHTMLWriterTest, Test) {
+ MessageLoop message_loop;
+ ChromeThread fake_ui_thread(ChromeThread::UI, &message_loop);
+ ChromeThread fake_file_thread(ChromeThread::FILE, &message_loop);
+
+ TestingProfile profile;
+ profile.CreateHistoryService(true, false);
+ profile.BlockUntilHistoryProcessesPendingRequests();
+ profile.CreateFaviconService();
+ profile.CreateBookmarkModel(true);
+ profile.BlockUntilBookmarkModelLoaded();
+ BookmarkModel* model = profile.GetBookmarkModel();
+
+ // Create test PNG representing favicon for url1.
+ SkBitmap bitmap;
+ MakeTestSkBitmap(kIconWidth, kIconHeight, &bitmap);
+ std::vector<unsigned char> icon_data;
+ gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &icon_data);
+
// Populate the BookmarkModel. This creates the following bookmark structure:
// Bookmarks bar
// F1
@@ -126,41 +180,65 @@ TEST_F(BookmarkHTMLWriterTest, Test) {
std::wstring url3_title = L"url\"3";
std::wstring url4_title = L"url\"&;";
GURL url1("http://url1");
+ GURL url1_favicon("http://url1/icon.ico");
GURL url2("http://url2");
GURL url3("http://url3");
GURL url4("http://\"&;\"");
- BookmarkModel model(NULL);
base::Time t1(base::Time::Now());
base::Time t2(t1 + base::TimeDelta::FromHours(1));
base::Time t3(t1 + base::TimeDelta::FromHours(1));
base::Time t4(t1 + base::TimeDelta::FromHours(1));
- const BookmarkNode* f1 = model.AddGroup(
- model.GetBookmarkBarNode(), 0, f1_title);
- model.AddURLWithCreationTime(f1, 0, url1_title, url1, t1);
- const BookmarkNode* f2 = model.AddGroup(f1, 1, f2_title);
- model.AddURLWithCreationTime(f2, 0, url2_title, url2, t2);
- model.AddURLWithCreationTime(model.GetBookmarkBarNode(), 1, url3_title, url3,
- t3);
-
- model.AddURLWithCreationTime(model.other_node(), 0, url1_title, url1, t1);
- model.AddURLWithCreationTime(model.other_node(), 1, url2_title, url2, t2);
- const BookmarkNode* f3 = model.AddGroup(model.other_node(), 2, f3_title);
- const BookmarkNode* f4 = model.AddGroup(f3, 0, f4_title);
- model.AddURLWithCreationTime(f4, 0, url1_title, url1, t1);
- model.AddURLWithCreationTime(model.GetBookmarkBarNode(), 2, url4_title,
- url4, t4);
+ const BookmarkNode* f1 = model->AddGroup(
+ model->GetBookmarkBarNode(), 0, f1_title);
+ model->AddURLWithCreationTime(f1, 0, url1_title, url1, t1);
+ profile.GetHistoryService(Profile::EXPLICIT_ACCESS)->AddPage(url1);
+ profile.GetFaviconService(Profile::EXPLICIT_ACCESS)->SetFavicon(url1,
+ url1_favicon,
+ icon_data);
+ message_loop.RunAllPending();
+ const BookmarkNode* f2 = model->AddGroup(f1, 1, f2_title);
+ model->AddURLWithCreationTime(f2, 0, url2_title, url2, t2);
+ model->AddURLWithCreationTime(model->GetBookmarkBarNode(),
+ 1, url3_title, url3, t3);
+
+ model->AddURLWithCreationTime(model->other_node(), 0, url1_title, url1, t1);
+ model->AddURLWithCreationTime(model->other_node(), 1, url2_title, url2, t2);
+ const BookmarkNode* f3 = model->AddGroup(model->other_node(), 2, f3_title);
+ const BookmarkNode* f4 = model->AddGroup(f3, 0, f4_title);
+ model->AddURLWithCreationTime(f4, 0, url1_title, url1, t1);
+ model->AddURLWithCreationTime(model->GetBookmarkBarNode(), 2, url4_title,
+ url4, t4);
// Write to a temp file.
- MessageLoop message_loop;
- ChromeThread fake_file_thread(ChromeThread::FILE, &message_loop);
- bookmark_html_writer::WriteBookmarks(&model, path_);
+ BookmarksObserver observer(&message_loop);
+ bookmark_html_writer::WriteBookmarks(&profile, path_, &observer);
+ message_loop.Run();
+
+ // Clear favicon so that it would be read from file.
+ std::vector<unsigned char> empty_data;
+ profile.GetFaviconService(Profile::EXPLICIT_ACCESS)->SetFavicon(url1,
+ url1_favicon,
+ empty_data);
message_loop.RunAllPending();
// Read the bookmarks back in.
std::vector<ProfileWriter::BookmarkEntry> parsed_bookmarks;
+ std::vector<history::ImportedFavIconUsage> favicons;
Firefox2Importer::ImportBookmarksFile(path_.ToWStringHack(), std::set<GURL>(),
false, L"x", NULL, &parsed_bookmarks,
- NULL, NULL);
+ NULL, &favicons);
+
+ // Check loaded favicon (url1 is represents by 3 separate bookmarks).
+ EXPECT_EQ(3U, favicons.size());
+ for (size_t i = 0; i < favicons.size(); i++) {
+ if (url1_favicon == favicons[i].favicon_url) {
+ EXPECT_EQ(1U, favicons[i].urls.size());
+ std::set<GURL>::const_iterator iter = favicons[i].urls.find(url1);
+ ASSERT_TRUE(iter != favicons[i].urls.end());
+ ASSERT_TRUE(*iter == url1);
+ ASSERT_TRUE(favicons[i].png_data == icon_data);
+ }
+ }
// Verify we got back what we wrote.
ASSERT_EQ(7U, parsed_bookmarks.size());