diff options
author | nkostylev@google.com <nkostylev@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-08 16:02:56 +0000 |
---|---|---|
committer | nkostylev@google.com <nkostylev@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-08 16:02:56 +0000 |
commit | d2879af4320bd17bd9082bf724f5222f6060f3d0 (patch) | |
tree | a3a9a853909b4bb332d22ab4db0e01ff5a6e28c0 /chrome/browser/bookmarks | |
parent | 1e6e3c99f8ca2ef9d8e86ddddec20dadc0fbf467 (diff) | |
download | chromium_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.cc | 184 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_html_writer.h | 99 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_html_writer_unittest.cc | 120 |
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()); |