summaryrefslogtreecommitdiffstats
path: root/chrome/browser/bookmarks
diff options
context:
space:
mode:
authorsky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-09 23:08:13 +0000
committersky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-09 23:08:13 +0000
commit68de8b76186059157dc916c499923d46bedb56b1 (patch)
tree870704b95740f2cafc144ced59b6bbf3c77c39d1 /chrome/browser/bookmarks
parentd9b168764a9e7ad7dcd2561791041f660a54cdde (diff)
downloadchromium_src-68de8b76186059157dc916c499923d46bedb56b1.zip
chromium_src-68de8b76186059157dc916c499923d46bedb56b1.tar.gz
chromium_src-68de8b76186059157dc916c499923d46bedb56b1.tar.bz2
Moves bookmark related classes into bookmarks directory. There are no
code changes here (other than converting to COPY_AND_BLAH_BLAH and updating include guards). BUG=none TEST=none Review URL: http://codereview.chromium.org/1868 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@1944 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/bookmarks')
-rw-r--r--chrome/browser/bookmarks/bookmark_bar_model.cc694
-rw-r--r--chrome/browser/bookmarks/bookmark_bar_model.h435
-rw-r--r--chrome/browser/bookmarks/bookmark_bar_model_unittest.cc721
-rw-r--r--chrome/browser/bookmarks/bookmark_codec.cc195
-rw-r--r--chrome/browser/bookmarks/bookmark_codec.h67
-rw-r--r--chrome/browser/bookmarks/bookmark_drag_data.cc118
-rw-r--r--chrome/browser/bookmarks/bookmark_drag_data.h81
-rw-r--r--chrome/browser/bookmarks/bookmark_drag_data_unittest.cc120
-rw-r--r--chrome/browser/bookmarks/bookmark_storage.cc175
-rw-r--r--chrome/browser/bookmarks/bookmark_storage.h97
10 files changed, 2703 insertions, 0 deletions
diff --git a/chrome/browser/bookmarks/bookmark_bar_model.cc b/chrome/browser/bookmarks/bookmark_bar_model.cc
new file mode 100644
index 0000000..df35fe5
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_bar_model.cc
@@ -0,0 +1,694 @@
+// Copyright (c) 2006-2008 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_bar_model.h"
+
+#include "base/gfx/png_decoder.h"
+#include "chrome/browser/bookmarks/bookmark_storage.h"
+#include "chrome/browser/history/query_parser.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/scoped_vector.h"
+
+#include "generated_resources.h"
+
+namespace {
+
+// Functions used for sorting.
+bool MoreRecentlyModified(BookmarkBarNode* n1, BookmarkBarNode* n2) {
+ return n1->date_group_modified() > n2->date_group_modified();
+}
+
+bool MoreRecentlyAdded(BookmarkBarNode* n1, BookmarkBarNode* n2) {
+ return n1->date_added() > n2->date_added();
+}
+
+} // namespace
+
+// BookmarkBarNode ------------------------------------------------------------
+
+namespace {
+
+// ID for BookmarkBarNodes.
+// Various places assume an invalid id if == 0, for that reason we start with 1.
+int next_id_ = 1;
+
+}
+
+const SkBitmap& BookmarkBarNode::GetFavIcon() {
+ if (!loaded_favicon_) {
+ loaded_favicon_ = true;
+ model_->LoadFavIcon(this);
+ }
+ return favicon_;
+}
+
+BookmarkBarNode::BookmarkBarNode(BookmarkBarModel* model, const GURL& url)
+ : model_(model),
+ id_(next_id_++),
+ loaded_favicon_(false),
+ favicon_load_handle_(0),
+ url_(url),
+ type_(!url.is_empty() ? history::StarredEntry::URL :
+ history::StarredEntry::BOOKMARK_BAR),
+ date_added_(Time::Now()) {
+}
+
+void BookmarkBarNode::Reset(const history::StarredEntry& entry) {
+ DCHECK(entry.type != history::StarredEntry::URL ||
+ entry.url == url_);
+
+ favicon_ = SkBitmap();
+ type_ = entry.type;
+ date_added_ = entry.date_added;
+ date_group_modified_ = entry.date_group_modified;
+ SetTitle(entry.title);
+}
+
+// BookmarkBarModel -----------------------------------------------------------
+
+BookmarkBarModel::BookmarkBarModel(Profile* profile)
+ : profile_(profile),
+ loaded_(false),
+#pragma warning(suppress: 4355) // Okay to pass "this" here.
+ root_(this, GURL()),
+ bookmark_bar_node_(NULL),
+ other_node_(NULL),
+ waiting_for_history_load_(false),
+ loaded_signal_(CreateEvent(NULL, TRUE, FALSE, NULL)) {
+ // Create the bookmark bar and other bookmarks folders. These always exist.
+ CreateBookmarkBarNode();
+ CreateOtherBookmarksNode();
+
+ // And add them to the root.
+ //
+ // WARNING: order is important here, various places assume bookmark bar then
+ // other node.
+ root_.Add(0, bookmark_bar_node_);
+ root_.Add(1, other_node_);
+
+ if (!profile_) {
+ // Profile is null during testing.
+ DoneLoading();
+ }
+}
+
+BookmarkBarModel::~BookmarkBarModel() {
+ if (profile_ && store_.get()) {
+ NotificationService::current()->RemoveObserver(
+ this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_));
+ }
+
+ if (waiting_for_history_load_) {
+ NotificationService::current()->RemoveObserver(
+ this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_));
+ }
+
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkModelBeingDeleted(this));
+
+ if (store_) {
+ // The store maintains a reference back to us. We need to tell it we're gone
+ // so that it doesn't try and invoke a method back on us again.
+ store_->BookmarkModelDeleted();
+ }
+}
+
+void BookmarkBarModel::Load() {
+ if (store_.get()) {
+ // If the store is non-null, it means Load was already invoked. Load should
+ // only be invoked once.
+ NOTREACHED();
+ return;
+ }
+
+ // Listen for changes to favicons so that we can update the favicon of the
+ // node appropriately.
+ NotificationService::current()->AddObserver(
+ this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_));
+
+ // Load the bookmarks. BookmarkStorage notifies us when done.
+ store_ = new BookmarkStorage(profile_, this);
+ store_->LoadBookmarks(false);
+}
+
+BookmarkBarNode* BookmarkBarModel::GetParentForNewNodes() {
+ std::vector<BookmarkBarNode*> nodes;
+
+ GetMostRecentlyModifiedGroupNodes(&root_, 1, &nodes);
+ return nodes.empty() ? bookmark_bar_node_ : nodes[0];
+}
+
+std::vector<BookmarkBarNode*> BookmarkBarModel::GetMostRecentlyModifiedGroups(
+ size_t max_count) {
+ std::vector<BookmarkBarNode*> nodes;
+ GetMostRecentlyModifiedGroupNodes(&root_, max_count, &nodes);
+
+ if (nodes.size() < max_count) {
+ // Add the bookmark bar and other nodes if there is space.
+ if (find(nodes.begin(), nodes.end(), bookmark_bar_node_) == nodes.end())
+ nodes.push_back(bookmark_bar_node_);
+
+ if (nodes.size() < max_count &&
+ find(nodes.begin(), nodes.end(), other_node_) == nodes.end()) {
+ nodes.push_back(other_node_);
+ }
+ }
+ return nodes;
+}
+
+void BookmarkBarModel::GetMostRecentlyAddedEntries(
+ size_t count,
+ std::vector<BookmarkBarNode*>* nodes) {
+ AutoLock url_lock(url_lock_);
+ for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
+ i != nodes_ordered_by_url_set_.end(); ++i) {
+ std::vector<BookmarkBarNode*>::iterator insert_position =
+ std::upper_bound(nodes->begin(), nodes->end(), *i, &MoreRecentlyAdded);
+ if (nodes->size() < count || insert_position != nodes->end()) {
+ nodes->insert(insert_position, *i);
+ while (nodes->size() > count)
+ nodes->pop_back();
+ }
+ }
+}
+
+void BookmarkBarModel::GetBookmarksMatchingText(
+ const std::wstring& text,
+ size_t max_count,
+ std::vector<TitleMatch>* matches) {
+ QueryParser parser;
+ ScopedVector<QueryNode> query_nodes;
+ parser.ParseQuery(text, &query_nodes.get());
+ if (query_nodes.empty())
+ return;
+
+ AutoLock url_lock(url_lock_);
+ Snippet::MatchPositions match_position;
+ for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
+ i != nodes_ordered_by_url_set_.end(); ++i) {
+ if (parser.DoesQueryMatch((*i)->GetTitle(), query_nodes.get(),
+ &match_position)) {
+ matches->push_back(TitleMatch());
+ matches->back().node = *i;
+ matches->back().match_positions.swap(match_position);
+ if (matches->size() == max_count)
+ break;
+ }
+ }
+}
+
+void BookmarkBarModel::Remove(BookmarkBarNode* parent, int index) {
+ if (!loaded_ || !IsValidIndex(parent, index, false) || parent == &root_) {
+ NOTREACHED();
+ return;
+ }
+ RemoveAndDeleteNode(parent->GetChild(index));
+}
+
+void BookmarkBarModel::Move(BookmarkBarNode* node,
+ BookmarkBarNode* new_parent,
+ int index) {
+ if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
+ new_parent == &root_ || node == &root_ || node == bookmark_bar_node_ ||
+ node == other_node_) {
+ NOTREACHED();
+ return;
+ }
+
+ if (new_parent->HasAncestor(node)) {
+ // Can't make an ancestor of the node be a child of the node.
+ NOTREACHED();
+ return;
+ }
+
+ SetDateGroupModified(new_parent, Time::Now());
+
+ BookmarkBarNode* old_parent = node->GetParent();
+ int old_index = old_parent->IndexOfChild(node);
+
+ if (old_parent == new_parent &&
+ (index == old_index || index == old_index + 1)) {
+ // Node is already in this position, nothing to do.
+ return;
+ }
+
+ if (old_parent == new_parent && index > old_index)
+ index--;
+ new_parent->Add(index, node);
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkNodeMoved(this, old_parent, old_index,
+ new_parent, index));
+}
+
+void BookmarkBarModel::SetTitle(BookmarkBarNode* node,
+ const std::wstring& title) {
+ if (!node) {
+ NOTREACHED();
+ return;
+ }
+ if (node->GetTitle() == title)
+ return;
+
+ node->SetTitle(title);
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkNodeChanged(this, node));
+}
+
+BookmarkBarNode* BookmarkBarModel::GetNodeByURL(const GURL& url) {
+ AutoLock url_lock(url_lock_);
+ BookmarkBarNode tmp_node(this, url);
+ NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
+ return (i != nodes_ordered_by_url_set_.end()) ? *i : NULL;
+}
+
+void BookmarkBarModel::GetBookmarks(std::vector<GURL>* urls) {
+ AutoLock url_lock(url_lock_);
+ for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
+ i != nodes_ordered_by_url_set_.end(); ++i) {
+ urls->push_back((*i)->GetURL());
+ }
+}
+
+BookmarkBarNode* BookmarkBarModel::GetNodeByID(int id) {
+ // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
+ return GetNodeByID(&root_, id);
+}
+
+BookmarkBarNode* BookmarkBarModel::AddGroup(
+ BookmarkBarNode* parent,
+ int index,
+ const std::wstring& title) {
+ if (!loaded_ || parent == &root_ || !IsValidIndex(parent, index, true)) {
+ // Can't add to the root.
+ NOTREACHED();
+ return NULL;
+ }
+
+ BookmarkBarNode* new_node = new BookmarkBarNode(this, GURL());
+ new_node->SetTitle(title);
+ new_node->type_ = history::StarredEntry::USER_GROUP;
+
+ return AddNode(parent, index, new_node);
+}
+
+BookmarkBarNode* BookmarkBarModel::AddURL(BookmarkBarNode* parent,
+ int index,
+ const std::wstring& title,
+ const GURL& url) {
+ return AddURLWithCreationTime(parent, index, title, url, Time::Now());
+}
+
+BookmarkBarNode* BookmarkBarModel::AddURLWithCreationTime(
+ BookmarkBarNode* parent,
+ int index,
+ const std::wstring& title,
+ const GURL& url,
+ const Time& creation_time) {
+ if (!loaded_ || !url.is_valid() || parent == &root_ ||
+ !IsValidIndex(parent, index, true)) {
+ NOTREACHED();
+ return NULL;
+ }
+
+ BookmarkBarNode* existing_node = GetNodeByURL(url);
+ if (existing_node) {
+ Move(existing_node, parent, index);
+ SetTitle(existing_node, title);
+ return existing_node;
+ }
+
+ SetDateGroupModified(parent, creation_time);
+
+ BookmarkBarNode* new_node = new BookmarkBarNode(this, url);
+ new_node->SetTitle(title);
+ new_node->date_added_ = creation_time;
+ new_node->type_ = history::StarredEntry::URL;
+
+ AutoLock url_lock(url_lock_);
+ nodes_ordered_by_url_set_.insert(new_node);
+
+ return AddNode(parent, index, new_node);
+}
+
+void BookmarkBarModel::SetURLStarred(const GURL& url,
+ const std::wstring& title,
+ bool is_starred) {
+ BookmarkBarNode* node = GetNodeByURL(url);
+ if (is_starred && !node) {
+ // Add the url.
+ BookmarkBarNode* parent = GetParentForNewNodes();
+ AddURL(parent, parent->GetChildCount(), title, url);
+ } else if (!is_starred && node) {
+ Remove(node->GetParent(), node->GetParent()->IndexOfChild(node));
+ }
+}
+
+void BookmarkBarModel::ResetDateGroupModified(BookmarkBarNode* node) {
+ SetDateGroupModified(node, Time());
+}
+
+void BookmarkBarModel::FavIconLoaded(BookmarkBarNode* node) {
+ // Send out notification to the observer.
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkNodeFavIconLoaded(this, node));
+}
+
+void BookmarkBarModel::RemoveNode(BookmarkBarNode* node,
+ std::set<GURL>* removed_urls) {
+ if (!loaded_ || !node || node == &root_ || node == bookmark_bar_node_ ||
+ node == other_node_) {
+ NOTREACHED();
+ return;
+ }
+
+ if (node->GetType() == history::StarredEntry::URL) {
+ // NOTE: this is called in such a way that url_lock_ is already held. As
+ // such, this doesn't explicitly grab the lock.
+ NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
+ DCHECK(i != nodes_ordered_by_url_set_.end());
+ nodes_ordered_by_url_set_.erase(i);
+ removed_urls->insert(node->GetURL());
+ }
+
+ CancelPendingFavIconLoadRequests(node);
+
+ // Recurse through children.
+ for (int i = node->GetChildCount() - 1; i >= 0; --i)
+ RemoveNode(node->GetChild(i), removed_urls);
+}
+
+void BookmarkBarModel::OnBookmarkStorageLoadedBookmarks(
+ bool file_exists,
+ bool loaded_from_history) {
+ if (loaded_) {
+ NOTREACHED();
+ return;
+ }
+
+ if (file_exists || loaded_from_history || !profile_ ||
+ !profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)) {
+ // The file exists, we're loaded.
+ DoneLoading();
+
+ if (loaded_from_history) {
+ // We were just populated from the historical file. Schedule a save so
+ // that the main file is up to date.
+ store_->ScheduleSave();
+ }
+ return;
+ }
+
+ // The file doesn't exist. This means one of two things:
+ // 1. A clean profile.
+ // 2. The user is migrating from an older version where bookmarks were saved
+ // in history.
+ // We assume step 2. If history had the bookmarks, history will write the
+ // bookmarks to a file for us. We need to wait until history has finished
+ // loading before reading from that file.
+ HistoryService* history =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (!history->backend_loaded()) {
+ // The backend isn't finished loading. Wait for it.
+ waiting_for_history_load_ = true;
+ NotificationService::current()->AddObserver(
+ this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_));
+ } else {
+ OnHistoryDone();
+ }
+}
+
+void BookmarkBarModel::OnHistoryDone() {
+ if (loaded_) {
+ NOTREACHED();
+ return;
+ }
+
+ // If the bookmarks were stored in the db the db will have migrated them to
+ // a file now. Try loading from the file.
+ store_->LoadBookmarks(true);
+}
+
+void BookmarkBarModel::DoneLoading() {
+ {
+ AutoLock url_lock(url_lock_);
+ // Update nodes_ordered_by_url_set_ from the nodes.
+ PopulateNodesByURL(&root_);
+ }
+
+ loaded_ = true;
+
+ if (loaded_signal_.Get())
+ SetEvent(loaded_signal_.Get());
+
+
+ // Notify our direct observers.
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, Loaded(this));
+
+ // And generic notification.
+ NotificationService::current()->Notify(
+ NOTIFY_BOOKMARK_MODEL_LOADED,
+ Source<Profile>(profile_),
+ NotificationService::NoDetails());
+}
+
+void BookmarkBarModel::RemoveAndDeleteNode(BookmarkBarNode* delete_me) {
+ scoped_ptr<BookmarkBarNode> node(delete_me);
+
+ BookmarkBarNode* parent = node->GetParent();
+ DCHECK(parent);
+ int index = parent->IndexOfChild(node.get());
+ parent->Remove(index);
+ history::URLsStarredDetails details(false);
+ {
+ AutoLock url_lock(url_lock_);
+ RemoveNode(node.get(), &details.changed_urls);
+ }
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkNodeRemoved(this, parent, index));
+
+ if (profile_) {
+ HistoryService* history =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (history)
+ history->URLsNoLongerBookmarked(details.changed_urls);
+ }
+
+ NotificationService::current()->Notify(NOTIFY_URLS_STARRED,
+ Source<Profile>(profile_),
+ Details<history::URLsStarredDetails>(&details));
+}
+
+BookmarkBarNode* BookmarkBarModel::AddNode(BookmarkBarNode* parent,
+ int index,
+ BookmarkBarNode* node) {
+ parent->Add(index, node);
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkNodeAdded(this, parent, index));
+
+ if (node->GetType() == history::StarredEntry::URL) {
+ history::URLsStarredDetails details(true);
+ details.changed_urls.insert(node->GetURL());
+ NotificationService::current()->Notify(NOTIFY_URLS_STARRED,
+ Source<Profile>(profile_),
+ Details<history::URLsStarredDetails>(&details));
+ }
+ return node;
+}
+
+void BookmarkBarModel::BlockTillLoaded() {
+ if (loaded_signal_.Get())
+ WaitForSingleObject(loaded_signal_.Get(), INFINITE);
+}
+
+BookmarkBarNode* BookmarkBarModel::GetNodeByID(BookmarkBarNode* node,
+ int id) {
+ if (node->id() == id)
+ return node;
+
+ for (int i = 0; i < node->GetChildCount(); ++i) {
+ BookmarkBarNode* result = GetNodeByID(node->GetChild(i), id);
+ if (result)
+ return result;
+ }
+ return NULL;
+}
+
+bool BookmarkBarModel::IsValidIndex(BookmarkBarNode* parent,
+ int index,
+ bool allow_end) {
+ return (parent &&
+ (index >= 0 && (index < parent->GetChildCount() ||
+ (allow_end && index == parent->GetChildCount()))));
+ }
+
+void BookmarkBarModel::SetDateGroupModified(BookmarkBarNode* parent,
+ const Time time) {
+ DCHECK(parent);
+ parent->date_group_modified_ = time;
+
+ if (store_.get())
+ store_->ScheduleSave();
+}
+
+void BookmarkBarModel::CreateBookmarkBarNode() {
+ history::StarredEntry entry;
+ entry.type = history::StarredEntry::BOOKMARK_BAR;
+ bookmark_bar_node_ = CreateRootNodeFromStarredEntry(entry);
+}
+
+void BookmarkBarModel::CreateOtherBookmarksNode() {
+ history::StarredEntry entry;
+ entry.type = history::StarredEntry::OTHER;
+ other_node_ = CreateRootNodeFromStarredEntry(entry);
+}
+
+BookmarkBarNode* BookmarkBarModel::CreateRootNodeFromStarredEntry(
+ const history::StarredEntry& entry) {
+ DCHECK(entry.type == history::StarredEntry::BOOKMARK_BAR ||
+ entry.type == history::StarredEntry::OTHER);
+ BookmarkBarNode* node = new BookmarkBarNode(this, GURL());
+ node->Reset(entry);
+ if (entry.type == history::StarredEntry::BOOKMARK_BAR)
+ node->SetTitle(l10n_util::GetString(IDS_BOOMARK_BAR_FOLDER_NAME));
+ else
+ node->SetTitle(l10n_util::GetString(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
+ return node;
+}
+
+void BookmarkBarModel::OnFavIconDataAvailable(
+ HistoryService::Handle handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedBytes> data,
+ bool expired,
+ GURL icon_url) {
+ SkBitmap fav_icon;
+ BookmarkBarNode* node =
+ load_consumer_.GetClientData(
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS), handle);
+ DCHECK(node);
+ node->favicon_load_handle_ = 0;
+ if (know_favicon && data.get() &&
+ PNGDecoder::Decode(&data->data, &fav_icon)) {
+ node->favicon_ = fav_icon;
+ FavIconLoaded(node);
+ }
+}
+
+void BookmarkBarModel::LoadFavIcon(BookmarkBarNode* node) {
+ if (node->GetType() != history::StarredEntry::URL)
+ return;
+
+ DCHECK(node->GetURL().is_valid());
+ HistoryService* history_service =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (!history_service)
+ return;
+
+ HistoryService::Handle handle = history_service->GetFavIconForURL(
+ node->GetURL(), &load_consumer_,
+ NewCallback(this, &BookmarkBarModel::OnFavIconDataAvailable));
+ load_consumer_.SetClientData(history_service, handle, node);
+ node->favicon_load_handle_ = handle;
+}
+
+void BookmarkBarModel::CancelPendingFavIconLoadRequests(BookmarkBarNode* node) {
+ if (node->favicon_load_handle_) {
+ HistoryService* history =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (history)
+ history->CancelRequest(node->favicon_load_handle_);
+ node->favicon_load_handle_ = 0;
+ }
+}
+
+void BookmarkBarModel::GetMostRecentlyModifiedGroupNodes(
+ BookmarkBarNode* parent,
+ size_t count,
+ std::vector<BookmarkBarNode*>* nodes) {
+ if (parent != &root_ && parent->is_folder() &&
+ parent->date_group_modified() > Time()) {
+ if (count == 0) {
+ nodes->push_back(parent);
+ } else {
+ std::vector<BookmarkBarNode*>::iterator i =
+ std::upper_bound(nodes->begin(), nodes->end(), parent,
+ &MoreRecentlyModified);
+ if (nodes->size() < count || i != nodes->end()) {
+ nodes->insert(i, parent);
+ while (nodes->size() > count)
+ nodes->pop_back();
+ }
+ }
+ } // else case, the root node, which we don't care about or imported nodes
+ // (which have a time of 0).
+ for (int i = 0; i < parent->GetChildCount(); ++i) {
+ BookmarkBarNode* child = parent->GetChild(i);
+ if (child->is_folder())
+ GetMostRecentlyModifiedGroupNodes(child, count, nodes);
+ }
+}
+
+void BookmarkBarModel::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case NOTIFY_FAVICON_CHANGED: {
+ // Prevent the observers from getting confused for multiple favicon loads.
+ Details<history::FavIconChangeDetails> favicon_details(details);
+ for (std::set<GURL>::const_iterator i = favicon_details->urls.begin();
+ i != favicon_details->urls.end(); ++i) {
+ BookmarkBarNode* node = GetNodeByURL(*i);
+ if (node) {
+ // Got an updated favicon, for a URL, do a new request.
+ node->InvalidateFavicon();
+ CancelPendingFavIconLoadRequests(node);
+ FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
+ BookmarkNodeChanged(this, node));
+ }
+ }
+ break;
+ }
+
+ case NOTIFY_HISTORY_LOADED: {
+ if (waiting_for_history_load_) {
+ waiting_for_history_load_ = false;
+ NotificationService::current()->RemoveObserver(
+ this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_));
+ OnHistoryDone();
+ } else {
+ NOTREACHED();
+ }
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void BookmarkBarModel::PopulateNodesByURL(BookmarkBarNode* node) {
+ // NOTE: this is called with url_lock_ already held. As such, this doesn't
+ // explicitly grab the lock.
+ if (node->is_url())
+ nodes_ordered_by_url_set_.insert(node);
+ for (int i = 0; i < node->GetChildCount(); ++i)
+ PopulateNodesByURL(node->GetChild(i));
+}
diff --git a/chrome/browser/bookmarks/bookmark_bar_model.h b/chrome/browser/bookmarks/bookmark_bar_model.h
new file mode 100644
index 0000000..25c19ad
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_bar_model.h
@@ -0,0 +1,435 @@
+// Copyright (c) 2006-2008 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_BAR_MODEL_H_
+#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_BAR_MODEL_H_
+
+#include "base/lock.h"
+#include "base/observer_list.h"
+#include "base/scoped_handle.h"
+#include "chrome/browser/bookmarks/bookmark_service.h"
+#include "chrome/browser/bookmarks/bookmark_storage.h"
+#include "chrome/browser/cancelable_request.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/views/tree_node_model.h"
+#include "googleurl/src/gurl.h"
+#include "skia/include/SkBitmap.h"
+
+class BookmarkBarModel;
+class BookmarkCodec;
+class Profile;
+
+namespace history {
+class StarredURLDatabase;
+}
+
+// BookmarkBarNode ------------------------------------------------------------
+
+// BookmarkBarNode contains information about a starred entry: title, URL,
+// favicon, star id and type. BookmarkBarNodes are returned from a
+// BookmarkBarModel.
+//
+class BookmarkBarNode : public ChromeViews::TreeNode<BookmarkBarNode> {
+ friend class BookmarkBarModel;
+ friend class BookmarkCodec;
+ friend class history::StarredURLDatabase;
+ FRIEND_TEST(BookmarkBarModelTest, MostRecentlyAddedEntries);
+
+ public:
+ BookmarkBarNode(BookmarkBarModel* model, const GURL& url);
+ virtual ~BookmarkBarNode() {}
+
+ // Returns the favicon for the this node. If the favicon has not yet been
+ // loaded it is loaded and the observer of the model notified when done.
+ const SkBitmap& GetFavIcon();
+
+ // Returns the URL.
+ const GURL& GetURL() const { return url_; }
+
+ // Returns a unique id for this node.
+ //
+ // NOTE: this id is only unique for the session and NOT unique across
+ // sessions. Don't persist it!
+ int id() const { return id_; }
+
+ // Returns the type of this node.
+ history::StarredEntry::Type GetType() const { return type_; }
+
+ // Called when the favicon becomes invalid.
+ void InvalidateFavicon() {
+ loaded_favicon_ = false;
+ favicon_ = SkBitmap();
+ }
+
+ // Returns the time the bookmark/group was added.
+ Time date_added() const { return date_added_; }
+
+ // Returns the last time the group was modified. This is only maintained
+ // for folders (including the bookmark and other folder).
+ Time date_group_modified() const { return date_group_modified_; }
+
+ // Convenience for testing if this nodes represents a group. A group is
+ // a node whose type is not URL.
+ bool is_folder() const { return type_ != history::StarredEntry::URL; }
+
+ // Is this a URL?
+ bool is_url() const { return type_ == history::StarredEntry::URL; }
+
+ // TODO(sky): Consider adding last visit time here, it'll greatly simplify
+ // HistoryContentsProvider.
+
+ private:
+ // Resets the properties of the node from the supplied entry.
+ void Reset(const history::StarredEntry& entry);
+
+ // The model. This is NULL when created by StarredURLDatabase for migration.
+ BookmarkBarModel* model_;
+
+ // Unique identifier for this node.
+ const int id_;
+
+ // Whether the favicon has been loaded.
+ bool loaded_favicon_;
+
+ // The favicon.
+ SkBitmap favicon_;
+
+ // If non-zero, it indicates we're loading the favicon and this is the handle
+ // from the HistoryService.
+ HistoryService::Handle favicon_load_handle_;
+
+ // The URL. BookmarkBarModel maintains maps off this URL, it is important that
+ // it not change once the node has been created.
+ const GURL url_;
+
+ // Type of node.
+ // TODO(sky): bug 1256202, convert this into a type defined here.
+ history::StarredEntry::Type type_;
+
+ // Date we were created.
+ Time date_added_;
+
+ // Time last modified. Only used for groups.
+ Time date_group_modified_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BookmarkBarNode);
+};
+
+
+// BookmarkBarModelObserver ---------------------------------------------------
+
+// Observer for the BookmarkBarModel.
+//
+class BookmarkBarModelObserver {
+ public:
+ // Invoked when the model has finished loading.
+ virtual void Loaded(BookmarkBarModel* model) = 0;
+
+ // Invoked from the destructor of the BookmarkBarModel.
+ virtual void BookmarkModelBeingDeleted(BookmarkBarModel* model) { }
+
+ // Invoked when a node has moved.
+ virtual void BookmarkNodeMoved(BookmarkBarModel* model,
+ BookmarkBarNode* old_parent,
+ int old_index,
+ BookmarkBarNode* new_parent,
+ int new_index) = 0;
+
+ // Invoked when a node has been added.
+ virtual void BookmarkNodeAdded(BookmarkBarModel* model,
+ BookmarkBarNode* parent,
+ int index) = 0;
+
+ // Invoked when a node has been removed, the item may still be starred though.
+ virtual void BookmarkNodeRemoved(BookmarkBarModel* model,
+ BookmarkBarNode* parent,
+ int index) = 0;
+
+ // Invoked when the title or favicon of a node has changed.
+ virtual void BookmarkNodeChanged(BookmarkBarModel* model,
+ BookmarkBarNode* node) = 0;
+
+ // Invoked when a favicon has finished loading.
+ virtual void BookmarkNodeFavIconLoaded(BookmarkBarModel* model,
+ BookmarkBarNode* node) = 0;
+};
+
+// BookmarkBarModel -----------------------------------------------------------
+
+// BookmarkBarModel provides a directed acyclic graph of the starred entries
+// and groups. Two graphs are provided for the two entry points: those on
+// the bookmark bar, and those in the other folder.
+//
+// The methods of BookmarkBarModel update the internal structure immediately
+// and update the backend in the background.
+//
+// An observer may be attached to observer relevant events.
+//
+// You should NOT directly create a BookmarkBarModel, instead go through the
+// Profile.
+
+// TODO(sky): rename to BookmarkModel.
+class BookmarkBarModel : public NotificationObserver, public BookmarkService {
+ friend class BookmarkBarNode;
+ friend class BookmarkBarModelTest;
+ friend class BookmarkStorage;
+
+ public:
+ explicit BookmarkBarModel(Profile* profile);
+ virtual ~BookmarkBarModel();
+
+ // Loads the bookmarks. This is called by Profile upon creation of the
+ // BookmarkBarModel. You need not invoke this directly.
+ void Load();
+
+ // Returns the root node. The bookmark bar node and other node are children of
+ // the root node.
+ BookmarkBarNode* root_node() { return &root_; }
+
+ // Returns the bookmark bar node.
+ BookmarkBarNode* GetBookmarkBarNode() { return bookmark_bar_node_; }
+
+ // Returns the 'other' node.
+ BookmarkBarNode* other_node() { return other_node_; }
+
+ // Returns the parent the last node was added to. This never returns NULL
+ // (as long as the model is loaded).
+ BookmarkBarNode* GetParentForNewNodes();
+
+ // Returns a vector containing up to |max_count| of the most recently
+ // modified groups. This never returns an empty vector.
+ std::vector<BookmarkBarNode*> GetMostRecentlyModifiedGroups(size_t max_count);
+
+ // Returns the most recently added bookmarks.
+ void GetMostRecentlyAddedEntries(size_t count,
+ std::vector<BookmarkBarNode*>* nodes);
+
+ // Used by GetBookmarksMatchingText to return a matching node and the location
+ // of the match in the title.
+ struct TitleMatch {
+ BookmarkBarNode* node;
+
+ // Location of the matching words in the title of the node.
+ Snippet::MatchPositions match_positions;
+ };
+
+ // Returns the bookmarks whose title contains text. At most |max_count|
+ // matches are returned in |matches|.
+ void GetBookmarksMatchingText(const std::wstring& text,
+ size_t max_count,
+ std::vector<TitleMatch>* matches);
+
+ void AddObserver(BookmarkBarModelObserver* observer) {
+ observers_.AddObserver(observer);
+ }
+
+ void RemoveObserver(BookmarkBarModelObserver* observer) {
+ observers_.RemoveObserver(observer);
+ }
+
+ // Unstars or deletes the specified entry. Removing a group entry recursively
+ // unstars all nodes. Observers are notified immediately.
+ void Remove(BookmarkBarNode* parent, int index);
+
+ // Moves the specified entry to a new location.
+ void Move(BookmarkBarNode* node, BookmarkBarNode* new_parent, int index);
+
+ // Sets the title of the specified node.
+ void SetTitle(BookmarkBarNode* node, const std::wstring& title);
+
+ // Returns true if the model finished loading.
+ bool IsLoaded() { return loaded_; }
+
+ // Returns the node with the specified URL, or NULL if there is no node with
+ // the specified URL. This method is thread safe.
+ BookmarkBarNode* GetNodeByURL(const GURL& url);
+
+ // Returns all the bookmarked urls. This method is thread safe.
+ virtual void GetBookmarks(std::vector<GURL>* urls);
+
+ // Returns true if there is a bookmark for the specified URL. This method is
+ // thread safe. See BookmarkService for more details on this.
+ virtual bool IsBookmarked(const GURL& url) {
+ return GetNodeByURL(url) != NULL;
+ }
+
+ // Blocks until loaded; this is NOT invoked on the main thread. See
+ // BookmarkService for more details on this.
+ virtual void BlockTillLoaded();
+
+ // Returns the node with the specified id, or NULL if there is no node with
+ // the specified id.
+ BookmarkBarNode* GetNodeByID(int id);
+
+ // Adds a new group node at the specified position.
+ BookmarkBarNode* AddGroup(BookmarkBarNode* parent,
+ int index,
+ const std::wstring& title);
+
+ // Adds a url at the specified position. If there is already a node with the
+ // specified URL, it is moved to the new position.
+ BookmarkBarNode* AddURL(BookmarkBarNode* parent,
+ int index,
+ const std::wstring& title,
+ const GURL& url);
+
+ // Adds a url with a specific creation date.
+ BookmarkBarNode* AddURLWithCreationTime(BookmarkBarNode* parent,
+ int index,
+ const std::wstring& title,
+ const GURL& url,
+ const Time& creation_time);
+
+ // This is the convenience that makes sure the url is starred or not
+ // starred. If the URL is not currently starred, it is added to the
+ // most recent parent.
+ void SetURLStarred(const GURL& url,
+ const std::wstring& title,
+ bool is_starred);
+
+ // Resets the 'date modified' time of the node to 0. This is used during
+ // importing to exclude the newly created groups from showing up in the
+ // combobox of most recently modified groups.
+ void ResetDateGroupModified(BookmarkBarNode* node);
+
+ private:
+ // Used to order BookmarkBarNodes by URL.
+ class NodeURLComparator {
+ public:
+ bool operator()(BookmarkBarNode* n1, BookmarkBarNode* n2) const {
+ return n1->GetURL() < n2->GetURL();
+ }
+ };
+
+ // Overriden to notify the observer the favicon has been loaded.
+ void FavIconLoaded(BookmarkBarNode* node);
+
+ // Removes the node from internal maps and recurces through all children. If
+ // the node is a url, its url is added to removed_urls.
+ //
+ // This does NOT delete the node.
+ void RemoveNode(BookmarkBarNode* node, std::set<GURL>* removed_urls);
+
+ // Callback from BookmarkStorage that it has finished loading. This method
+ // may be hit twice. In particular, on construction BookmarkBarModel asks
+ // BookmarkStorage to load the bookmarks. BookmarkStorage invokes this method
+ // with loaded_from_history false and file_exists indicating whether the
+ // bookmarks file exists. If the file doesn't exist, we query history. When
+ // history calls us back (OnHistoryDone) we then ask BookmarkStorage to load
+ // from the migration file. BookmarkStorage again invokes this method, but
+ // with |loaded_from_history| true.
+ void OnBookmarkStorageLoadedBookmarks(bool file_exists,
+ bool loaded_from_history);
+
+ // Used for migrating bookmarks from history to standalone file.
+ //
+ // Callback from history that it is done with an empty request. This is used
+ // if there is no bookmarks file. Once done, we attempt to load from the
+ // temporary file creating during migration.
+ void OnHistoryDone();
+
+ // Invoked when loading is finished. Sets loaded_ and notifies observers.
+ void DoneLoading();
+
+ // Populates nodes_ordered_by_url_set_ from root.
+ void PopulateNodesByURL(BookmarkBarNode* node);
+
+ // Removes the node from its parent, sends notification, and deletes it.
+ // type specifies how the node should be removed.
+ void RemoveAndDeleteNode(BookmarkBarNode* delete_me);
+
+ // Adds the node at the specified position, and sends notification.
+ BookmarkBarNode* AddNode(BookmarkBarNode* parent,
+ int index,
+ BookmarkBarNode* node);
+
+ // Implementation of GetNodeByID.
+ BookmarkBarNode* GetNodeByID(BookmarkBarNode* node, int id);
+
+ // Returns true if the parent and index are valid.
+ bool IsValidIndex(BookmarkBarNode* parent, int index, bool allow_end);
+
+ // Sets the date modified time of the specified node.
+ void SetDateGroupModified(BookmarkBarNode* parent, const Time time);
+
+ // Creates the bookmark bar/other nodes. These call into
+ // CreateRootNodeFromStarredEntry.
+ void CreateBookmarkBarNode();
+ void CreateOtherBookmarksNode();
+
+ // Creates a root node (either the bookmark bar node or other node) from the
+ // specified starred entry.
+ BookmarkBarNode* CreateRootNodeFromStarredEntry(
+ const history::StarredEntry& entry);
+
+ // Notification that a favicon has finished loading. If we can decode the
+ // favicon, FaviconLoaded is invoked.
+ void OnFavIconDataAvailable(
+ HistoryService::Handle handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedBytes> data,
+ bool expired,
+ GURL icon_url);
+
+ // Invoked from the node to load the favicon. Requests the favicon from the
+ // history service.
+ void LoadFavIcon(BookmarkBarNode* node);
+
+ // If we're waiting on a favicon for node, the load request is canceled.
+ void CancelPendingFavIconLoadRequests(BookmarkBarNode* node);
+
+ // Returns up to count of the most recently modified groups. This may not
+ // add anything.
+ void GetMostRecentlyModifiedGroupNodes(BookmarkBarNode* parent,
+ size_t count,
+ std::vector<BookmarkBarNode*>* nodes);
+
+ // NotificationObserver.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ Profile* profile_;
+
+ // Whether the initial set of data has been loaded.
+ bool loaded_;
+
+ // The root node. This contains the bookmark bar node and the 'other' node as
+ // children.
+ BookmarkBarNode root_;
+
+ BookmarkBarNode* bookmark_bar_node_;
+ BookmarkBarNode* other_node_;
+
+ // The observers.
+ ObserverList<BookmarkBarModelObserver> observers_;
+
+ // Set of nodes ordered by URL. This is not a map to avoid copying the
+ // urls.
+ // WARNING: nodes_ordered_by_url_set_ is accessed on multiple threads. As
+ // such, be sure and wrap all usage of it around url_lock_.
+ typedef std::set<BookmarkBarNode*,NodeURLComparator> NodesOrderedByURLSet;
+ NodesOrderedByURLSet nodes_ordered_by_url_set_;
+ Lock url_lock_;
+
+ // Used for loading favicons and the empty history request.
+ CancelableRequestConsumerT<BookmarkBarNode*, NULL> load_consumer_;
+
+ // Reads/writes bookmarks to disk.
+ scoped_refptr<BookmarkStorage> store_;
+
+ // Have we installed a listener on the NotificationService for
+ // NOTIFY_HISTORY_LOADED? A listener is installed if the bookmarks file
+ // doesn't exist and the history service hasn't finished loading.
+ bool waiting_for_history_load_;
+
+ // Handle to event signaled when loading is done.
+ ScopedHandle loaded_signal_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkBarModel);
+};
+
+#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_BAR_MODEL_H_
diff --git a/chrome/browser/bookmarks/bookmark_bar_model_unittest.cc b/chrome/browser/bookmarks/bookmark_bar_model_unittest.cc
new file mode 100644
index 0000000..f6d4b2d
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_bar_model_unittest.cc
@@ -0,0 +1,721 @@
+// Copyright (c) 2006-2008 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 "base/string_util.h"
+#include "chrome/browser/bookmarks/bookmark_bar_model.h"
+#include "chrome/browser/bookmarks/bookmark_codec.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/testing_profile.h"
+#include "chrome/views/tree_node_model.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class BookmarkBarModelTest : public testing::Test,
+ public BookmarkBarModelObserver {
+ public:
+ struct ObserverDetails {
+ ObserverDetails() {
+ Set(NULL, NULL, -1, -1);
+ }
+
+ void Set(BookmarkBarNode* node1,
+ BookmarkBarNode* node2,
+ int index1,
+ int index2) {
+ this->node1 = node1;
+ this->node2 = node2;
+ this->index1 = index1;
+ this->index2 = index2;
+ }
+
+ void AssertEquals(BookmarkBarNode* node1,
+ BookmarkBarNode* node2,
+ int index1,
+ int index2) {
+ ASSERT_TRUE(this->node1 == node1);
+ ASSERT_TRUE(this->node2 == node2);
+ ASSERT_EQ(index1, this->index1);
+ ASSERT_EQ(index2, this->index2);
+ }
+
+ BookmarkBarNode* node1;
+ BookmarkBarNode* node2;
+ int index1;
+ int index2;
+ };
+
+ BookmarkBarModelTest() : model(NULL) {
+ model.AddObserver(this);
+ ClearCounts();
+ }
+
+
+ void Loaded(BookmarkBarModel* model) {
+ // We never load from the db, so that this should never get invoked.
+ NOTREACHED();
+ }
+
+ virtual void BookmarkNodeMoved(BookmarkBarModel* model,
+ BookmarkBarNode* old_parent,
+ int old_index,
+ BookmarkBarNode* new_parent,
+ int new_index) {
+ moved_count++;
+ observer_details.Set(old_parent, new_parent, old_index, new_index);
+ }
+
+ virtual void BookmarkNodeAdded(BookmarkBarModel* model,
+ BookmarkBarNode* parent,
+ int index) {
+ added_count++;
+ observer_details.Set(parent, NULL, index, -1);
+ }
+
+ virtual void BookmarkNodeRemoved(BookmarkBarModel* model,
+ BookmarkBarNode* parent,
+ int index) {
+ removed_count++;
+ observer_details.Set(parent, NULL, index, -1);
+ }
+
+ virtual void BookmarkNodeChanged(BookmarkBarModel* model,
+ BookmarkBarNode* node) {
+ changed_count++;
+ observer_details.Set(node, NULL, -1, -1);
+ }
+
+ virtual void BookmarkNodeFavIconLoaded(BookmarkBarModel* model,
+ BookmarkBarNode* node) {
+ // We never attempt to load favicons, so that this method never
+ // gets invoked.
+ }
+
+ void ClearCounts() {
+ moved_count = added_count = removed_count = changed_count = 0;
+ }
+
+ void AssertObserverCount(int added_count,
+ int moved_count,
+ int removed_count,
+ int changed_count) {
+ ASSERT_EQ(added_count, this->added_count);
+ ASSERT_EQ(moved_count, this->moved_count);
+ ASSERT_EQ(removed_count, this->removed_count);
+ ASSERT_EQ(changed_count, this->changed_count);
+ }
+
+ void AssertNodesEqual(BookmarkBarNode* expected, BookmarkBarNode* actual) {
+ ASSERT_TRUE(expected);
+ ASSERT_TRUE(actual);
+ EXPECT_EQ(expected->GetTitle(), actual->GetTitle());
+ EXPECT_EQ(expected->GetType(), actual->GetType());
+ EXPECT_TRUE(expected->date_added() == actual->date_added());
+ if (expected->GetType() == history::StarredEntry::URL) {
+ EXPECT_EQ(expected->GetURL(), actual->GetURL());
+ } else {
+ EXPECT_TRUE(expected->date_group_modified() ==
+ actual->date_group_modified());
+ ASSERT_EQ(expected->GetChildCount(), actual->GetChildCount());
+ for (int i = 0; i < expected->GetChildCount(); ++i)
+ AssertNodesEqual(expected->GetChild(i), actual->GetChild(i));
+ }
+ }
+
+ void AssertModelsEqual(BookmarkBarModel* expected,
+ BookmarkBarModel* actual) {
+ AssertNodesEqual(expected->GetBookmarkBarNode(),
+ actual->GetBookmarkBarNode());
+ AssertNodesEqual(expected->other_node(),
+ actual->other_node());
+ }
+
+ BookmarkBarModel model;
+
+ int moved_count;
+
+ int added_count;
+
+ int removed_count;
+
+ int changed_count;
+
+ ObserverDetails observer_details;
+};
+
+TEST_F(BookmarkBarModelTest, InitialState) {
+ BookmarkBarNode* bb_node = model.GetBookmarkBarNode();
+ ASSERT_TRUE(bb_node != NULL);
+ EXPECT_EQ(0, bb_node->GetChildCount());
+ EXPECT_EQ(history::StarredEntry::BOOKMARK_BAR, bb_node->GetType());
+
+ BookmarkBarNode* other_node = model.other_node();
+ ASSERT_TRUE(other_node != NULL);
+ EXPECT_EQ(0, other_node->GetChildCount());
+ EXPECT_EQ(history::StarredEntry::OTHER, other_node->GetType());
+
+ EXPECT_TRUE(bb_node->id() != other_node->id());
+}
+
+TEST_F(BookmarkBarModelTest, AddURL) {
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ const std::wstring title(L"foo");
+ const GURL url("http://foo.com");
+
+ BookmarkBarNode* new_node = model.AddURL(root, 0, title, url);
+ AssertObserverCount(1, 0, 0, 0);
+ observer_details.AssertEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->GetChildCount());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_TRUE(url == new_node->GetURL());
+ ASSERT_EQ(history::StarredEntry::URL, new_node->GetType());
+ ASSERT_TRUE(new_node == model.GetNodeByURL(url));
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model.other_node()->id());
+}
+
+TEST_F(BookmarkBarModelTest, AddGroup) {
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ const std::wstring title(L"foo");
+
+ BookmarkBarNode* new_node = model.AddGroup(root, 0, title);
+ AssertObserverCount(1, 0, 0, 0);
+ observer_details.AssertEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->GetChildCount());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_EQ(history::StarredEntry::USER_GROUP, new_node->GetType());
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model.other_node()->id());
+
+ // Add another group, just to make sure group_ids are incremented correctly.
+ ClearCounts();
+ BookmarkBarNode* new_node2 = model.AddGroup(root, 0, title);
+ AssertObserverCount(1, 0, 0, 0);
+ observer_details.AssertEquals(root, NULL, 0, -1);
+}
+
+TEST_F(BookmarkBarModelTest, RemoveURL) {
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ const std::wstring title(L"foo");
+ const GURL url("http://foo.com");
+ BookmarkBarNode* new_node = model.AddURL(root, 0, title, url);
+ ClearCounts();
+
+ model.Remove(root, 0);
+ ASSERT_EQ(0, root->GetChildCount());
+ AssertObserverCount(0, 0, 1, 0);
+ observer_details.AssertEquals(root, NULL, 0, -1);
+
+ // Make sure there is no mapping for the URL.
+ ASSERT_TRUE(model.GetNodeByURL(url) == NULL);
+}
+
+TEST_F(BookmarkBarModelTest, RemoveGroup) {
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ BookmarkBarNode* group = model.AddGroup(root, 0, L"foo");
+
+ ClearCounts();
+
+ // Add a URL as a child.
+ const std::wstring title(L"foo");
+ const GURL url("http://foo.com");
+ BookmarkBarNode* new_node = model.AddURL(group, 0, title, url);
+
+ ClearCounts();
+
+ // Now remove the group.
+ model.Remove(root, 0);
+ ASSERT_EQ(0, root->GetChildCount());
+ AssertObserverCount(0, 0, 1, 0);
+ observer_details.AssertEquals(root, NULL, 0, -1);
+
+ // Make sure there is no mapping for the URL.
+ ASSERT_TRUE(model.GetNodeByURL(url) == NULL);
+}
+
+TEST_F(BookmarkBarModelTest, SetTitle) {
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ std::wstring title(L"foo");
+ const GURL url("http://foo.com");
+ BookmarkBarNode* node = model.AddURL(root, 0, title, url);
+
+ ClearCounts();
+
+ title = L"foo2";
+ model.SetTitle(node, title);
+ AssertObserverCount(0, 0, 0, 1);
+ observer_details.AssertEquals(node, NULL, -1, -1);
+ EXPECT_EQ(title, node->GetTitle());
+}
+
+TEST_F(BookmarkBarModelTest, Move) {
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ std::wstring title(L"foo");
+ const GURL url("http://foo.com");
+ BookmarkBarNode* node = model.AddURL(root, 0, title, url);
+ BookmarkBarNode* group1 = model.AddGroup(root, 0, L"foo");
+ ClearCounts();
+
+ model.Move(node, group1, 0);
+
+ AssertObserverCount(0, 1, 0, 0);
+ observer_details.AssertEquals(root, group1, 1, 0);
+ EXPECT_TRUE(group1 == node->GetParent());
+ EXPECT_EQ(1, root->GetChildCount());
+ EXPECT_EQ(group1, root->GetChild(0));
+ EXPECT_EQ(1, group1->GetChildCount());
+ EXPECT_EQ(node, group1->GetChild(0));
+
+ // And remove the group.
+ ClearCounts();
+ model.Remove(root, 0);
+ AssertObserverCount(0, 0, 1, 0);
+ observer_details.AssertEquals(root, NULL, 0, -1);
+ EXPECT_TRUE(model.GetNodeByURL(url) == NULL);
+ EXPECT_EQ(0, root->GetChildCount());
+}
+
+// Tests that adding a URL to a folder updates the last modified time.
+TEST_F(BookmarkBarModelTest, ParentForNewNodes) {
+ ASSERT_EQ(model.GetBookmarkBarNode(), model.GetParentForNewNodes());
+
+ const std::wstring title(L"foo");
+ const GURL url("http://foo.com");
+
+ BookmarkBarNode* new_node =
+ model.AddURL(model.other_node(), 0, title, url);
+
+ ASSERT_EQ(model.other_node(), model.GetParentForNewNodes());
+}
+
+// Make sure recently modified stays in sync when adding a URL.
+TEST_F(BookmarkBarModelTest, MostRecentlyModifiedGroups) {
+ // Add a group.
+ BookmarkBarNode* group = model.AddGroup(model.other_node(), 0, L"foo");
+ // Add a URL to it.
+ model.AddURL(group, 0, L"blah", GURL("http://foo.com"));
+
+ // Make sure group is in the most recently modified.
+ std::vector<BookmarkBarNode*> most_recent_groups =
+ model.GetMostRecentlyModifiedGroups(1);
+ ASSERT_EQ(1, most_recent_groups.size());
+ ASSERT_EQ(group, most_recent_groups[0]);
+
+ // Nuke the group and do another fetch, making sure group isn't in the
+ // returned list.
+ model.Remove(group->GetParent(), 0);
+ most_recent_groups = model.GetMostRecentlyModifiedGroups(1);
+ ASSERT_EQ(1, most_recent_groups.size());
+ ASSERT_TRUE(most_recent_groups[0] != group);
+}
+
+// Make sure MostRecentlyAddedEntries stays in sync.
+TEST_F(BookmarkBarModelTest, MostRecentlyAddedEntries) {
+ // Add a couple of nodes such that the following holds for the time of the
+ // nodes: n1 > n2 > n3 > n4.
+ Time base_time = Time::Now();
+ BookmarkBarNode* n1 = model.AddURL(
+ model.GetBookmarkBarNode(), 0, L"blah", GURL("http://foo.com/0"));
+ BookmarkBarNode* n2 = model.AddURL(
+ model.GetBookmarkBarNode(), 1, L"blah", GURL("http://foo.com/1"));
+ BookmarkBarNode* n3 = model.AddURL(
+ model.GetBookmarkBarNode(), 2, L"blah", GURL("http://foo.com/2"));
+ BookmarkBarNode* n4 = model.AddURL(
+ model.GetBookmarkBarNode(), 3, L"blah", GURL("http://foo.com/3"));
+ n1->date_added_ = base_time + TimeDelta::FromDays(4);
+ n2->date_added_ = base_time + TimeDelta::FromDays(3);
+ n3->date_added_ = base_time + TimeDelta::FromDays(2);
+ n4->date_added_ = base_time + TimeDelta::FromDays(1);
+
+ // Make sure order is honored.
+ std::vector<BookmarkBarNode*> recently_added;
+ model.GetMostRecentlyAddedEntries(2, &recently_added);
+ ASSERT_EQ(2, recently_added.size());
+ ASSERT_TRUE(n1 == recently_added[0]);
+ ASSERT_TRUE(n2 == recently_added[1]);
+
+ // swap 1 and 2, then check again.
+ recently_added.clear();
+ std::swap(n1->date_added_, n2->date_added_);
+ model.GetMostRecentlyAddedEntries(4, &recently_added);
+ ASSERT_EQ(4, recently_added.size());
+ ASSERT_TRUE(n2 == recently_added[0]);
+ ASSERT_TRUE(n1 == recently_added[1]);
+ ASSERT_TRUE(n3 == recently_added[2]);
+ ASSERT_TRUE(n4 == recently_added[3]);
+}
+
+// Makes sure GetBookmarksMatchingText works.
+TEST_F(BookmarkBarModelTest, GetBookmarksMatchingText) {
+ // Add two urls with titles 'blah' and 'x' and one folder with the title
+ // 'blah'.
+ BookmarkBarNode* n1 = model.AddURL(
+ model.GetBookmarkBarNode(), 0, L"blah", GURL("http://foo.com/0"));
+ BookmarkBarNode* n2 = model.AddURL(
+ model.GetBookmarkBarNode(), 1, L"x", GURL("http://foo.com/1"));
+ model.AddGroup(model.GetBookmarkBarNode(), 2, L"blah");
+
+ // Make sure we don't get back the folder.
+ std::vector<BookmarkBarModel::TitleMatch> results;
+ model.GetBookmarksMatchingText(L"blah", 2, &results);
+ ASSERT_EQ(1U, results.size());
+ EXPECT_EQ(n1, results[0].node);
+ results.clear();
+
+ model.GetBookmarksMatchingText(L"x", 2, &results);
+ ASSERT_EQ(1U, results.size());
+ EXPECT_EQ(n2, results[0].node);
+}
+
+namespace {
+
+// See comment in PopulateNodeFromString.
+typedef ChromeViews::TreeNodeWithValue<history::StarredEntry::Type> TestNode;
+
+// Does the work of PopulateNodeFromString. index gives the index of the current
+// element in description to process.
+static void PopulateNodeImpl(const std::vector<std::wstring>& description,
+ size_t* index,
+ TestNode* parent) {
+ while (*index < description.size()) {
+ const std::wstring& element = description[*index];
+ (*index)++;
+ if (element == L"[") {
+ // Create a new group and recurse to add all the children.
+ // Groups are given a unique named by way of an ever increasing integer
+ // value. The groups need not have a name, but one is assigned to help
+ // in debugging.
+ static int next_group_id = 1;
+ TestNode* new_node =
+ new TestNode(IntToWString(next_group_id++),
+ history::StarredEntry::USER_GROUP);
+ parent->Add(parent->GetChildCount(), new_node);
+ PopulateNodeImpl(description, index, new_node);
+ } else if (element == L"]") {
+ // End the current group.
+ return;
+ } else {
+ // Add a new URL.
+
+ // All tokens must be space separated. If there is a [ or ] in the name it
+ // likely means a space was forgotten.
+ DCHECK(element.find('[') == std::string::npos);
+ DCHECK(element.find(']') == std::string::npos);
+ parent->Add(parent->GetChildCount(),
+ new TestNode(element, history::StarredEntry::URL));
+ }
+ }
+}
+
+// Creates and adds nodes to parent based on description. description consists
+// of the following tokens (all space separated):
+// [ : creates a new USER_GROUP node. All elements following the [ until the
+// next balanced ] is encountered are added as children to the node.
+// ] : closes the last group created by [ so that any further nodes are added
+// to the current groups parent.
+// text: creates a new URL node.
+// For example, "a [b] c" creates the following nodes:
+// a 1 c
+// |
+// b
+// In words: a node of type URL with the title a, followed by a group node with
+// the title 1 having the single child of type url with name b, followed by
+// the url node with the title c.
+//
+// NOTE: each name must be unique, and groups are assigned a unique title by way
+// of an increasing integer.
+static void PopulateNodeFromString(const std::wstring& description,
+ TestNode* parent) {
+ std::vector<std::wstring> elements;
+ size_t index = 0;
+ SplitStringAlongWhitespace(description, &elements);
+ PopulateNodeImpl(elements, &index, parent);
+}
+
+// Populates the BookmarkBarNode with the children of parent.
+static void PopulateBookmarkBarNode(TestNode* parent,
+ BookmarkBarModel* model,
+ BookmarkBarNode* bb_node) {
+ for (int i = 0; i < parent->GetChildCount(); ++i) {
+ TestNode* child = parent->GetChild(i);
+ if (child->value == history::StarredEntry::USER_GROUP) {
+ BookmarkBarNode* new_bb_node =
+ model->AddGroup(bb_node, i, child->GetTitle());
+ PopulateBookmarkBarNode(child, model, new_bb_node);
+ } else {
+ model->AddURL(bb_node, i, child->GetTitle(),
+ GURL("http://" + WideToASCII(child->GetTitle())));
+ }
+ }
+}
+
+} // namespace
+
+// Test class that creates a BookmarkBarModel with a real history backend.
+class BookmarkBarModelTestWithProfile : public testing::Test,
+ public BookmarkBarModelObserver {
+ public:
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ profile_.reset(NULL);
+ }
+
+ // The profile.
+ scoped_ptr<TestingProfile> profile_;
+
+ protected:
+ // Verifies the contents of the bookmark bar node match the contents of the
+ // TestNode.
+ void VerifyModelMatchesNode(TestNode* expected, BookmarkBarNode* actual) {
+ ASSERT_EQ(expected->GetChildCount(), actual->GetChildCount());
+ for (int i = 0; i < expected->GetChildCount(); ++i) {
+ TestNode* expected_child = expected->GetChild(i);
+ BookmarkBarNode* actual_child = actual->GetChild(i);
+ ASSERT_EQ(expected_child->GetTitle(), actual_child->GetTitle());
+ if (expected_child->value == history::StarredEntry::USER_GROUP) {
+ ASSERT_TRUE(actual_child->GetType() ==
+ history::StarredEntry::USER_GROUP);
+ // Recurse throught children.
+ VerifyModelMatchesNode(expected_child, actual_child);
+ if (HasFatalFailure())
+ return;
+ } else {
+ // No need to check the URL, just the title is enough.
+ ASSERT_TRUE(actual_child->GetType() ==
+ history::StarredEntry::URL);
+ }
+ }
+ }
+
+ void BlockTillBookmarkModelLoaded() {
+ bb_model_ = profile_->GetBookmarkBarModel();
+ if (!bb_model_->IsLoaded())
+ BlockTillLoaded(bb_model_);
+ else
+ bb_model_->AddObserver(this);
+ }
+
+ // Destroys the current profile, creates a new one and creates the history
+ // service.
+ void RecreateProfile() {
+ // Need to shutdown the old one before creating a new one.
+ profile_.reset(NULL);
+ profile_.reset(new TestingProfile());
+ profile_->CreateHistoryService(true);
+ }
+
+ BookmarkBarModel* bb_model_;
+
+ private:
+ // Blocks until the BookmarkBarModel has finished loading.
+ void BlockTillLoaded(BookmarkBarModel* model) {
+ model->AddObserver(this);
+ MessageLoop::current()->Run();
+ }
+
+ // BookmarkBarModelObserver methods.
+ virtual void Loaded(BookmarkBarModel* model) {
+ // Balances the call in BlockTillLoaded.
+ MessageLoop::current()->Quit();
+ }
+ virtual void BookmarkNodeMoved(BookmarkBarModel* model,
+ BookmarkBarNode* old_parent,
+ int old_index,
+ BookmarkBarNode* new_parent,
+ int new_index) {}
+ virtual void BookmarkNodeAdded(BookmarkBarModel* model,
+ BookmarkBarNode* parent,
+ int index) {}
+ virtual void BookmarkNodeRemoved(BookmarkBarModel* model,
+ BookmarkBarNode* parent,
+ int index) {}
+ virtual void BookmarkNodeChanged(BookmarkBarModel* model,
+ BookmarkBarNode* node) {}
+ virtual void BookmarkNodeFavIconLoaded(BookmarkBarModel* model,
+ BookmarkBarNode* node) {}
+
+ MessageLoopForUI message_loop_;
+};
+
+// Creates a set of nodes in the bookmark bar model, then recreates the
+// bookmark bar model which triggers loading from the db and checks the loaded
+// structure to make sure it is what we first created.
+TEST_F(BookmarkBarModelTestWithProfile, CreateAndRestore) {
+ struct TestData {
+ // Structure of the children of the bookmark bar model node.
+ const std::wstring bbn_contents;
+ // Structure of the children of the other node.
+ const std::wstring other_contents;
+ } data[] = {
+ // See PopulateNodeFromString for a description of these strings.
+ { L"", L"" },
+ { L"a", L"b" },
+ { L"a [ b ]", L"" },
+ { L"", L"[ b ] a [ c [ d e [ f ] ] ]" },
+ { L"a [ b ]", L"" },
+ { L"a b c [ d e [ f ] ]", L"g h i [ j k [ l ] ]"},
+ };
+ for (int i = 0; i < arraysize(data); ++i) {
+ // Recreate the profile. We need to reset with NULL first so that the last
+ // HistoryService releases the locks on the files it creates and we can
+ // delete them.
+ profile_.reset(NULL);
+ profile_.reset(new TestingProfile());
+ profile_->CreateBookmarkBarModel(true);
+ profile_->CreateHistoryService(true);
+ BlockTillBookmarkModelLoaded();
+
+ TestNode bbn;
+ PopulateNodeFromString(data[i].bbn_contents, &bbn);
+ PopulateBookmarkBarNode(&bbn, bb_model_, bb_model_->GetBookmarkBarNode());
+
+ TestNode other;
+ PopulateNodeFromString(data[i].other_contents, &other);
+ PopulateBookmarkBarNode(&other, bb_model_, bb_model_->other_node());
+
+ profile_->CreateBookmarkBarModel(false);
+ BlockTillBookmarkModelLoaded();
+
+ VerifyModelMatchesNode(&bbn, bb_model_->GetBookmarkBarNode());
+ VerifyModelMatchesNode(&other, bb_model_->other_node());
+ }
+}
+
+// Test class that creates a BookmarkBarModel with a real history backend.
+class BookmarkBarModelTestWithProfile2 :
+ public BookmarkBarModelTestWithProfile {
+ public:
+ virtual void SetUp() {
+ profile_.reset(new TestingProfile());
+ }
+
+ protected:
+ // Verifies the state of the model matches that of the state in the saved
+ // history file.
+ void VerifyExpectedState() {
+ // Here's the structure we expect:
+ // bbn
+ // www.google.com - Google
+ // F1
+ // http://www.google.com/intl/en/ads/ - Google Advertising
+ // F11
+ // http://www.google.com/services/ - Google Business Solutions
+ // other
+ // OF1
+ // http://www.google.com/intl/en/about.html - About Google
+ BookmarkBarNode* bbn = bb_model_->GetBookmarkBarNode();
+ ASSERT_EQ(2, bbn->GetChildCount());
+
+ BookmarkBarNode* child = bbn->GetChild(0);
+ ASSERT_EQ(history::StarredEntry::URL, child->GetType());
+ ASSERT_EQ(L"Google", child->GetTitle());
+ ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com"));
+
+ child = bbn->GetChild(1);
+ ASSERT_TRUE(child->is_folder());
+ ASSERT_EQ(L"F1", child->GetTitle());
+ ASSERT_EQ(2, child->GetChildCount());
+
+ BookmarkBarNode* parent = child;
+ child = parent->GetChild(0);
+ ASSERT_EQ(history::StarredEntry::URL, child->GetType());
+ ASSERT_EQ(L"Google Advertising", child->GetTitle());
+ ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com/intl/en/ads/"));
+
+ child = parent->GetChild(1);
+ ASSERT_TRUE(child->is_folder());
+ ASSERT_EQ(L"F11", child->GetTitle());
+ ASSERT_EQ(1, child->GetChildCount());
+
+ parent = child;
+ child = parent->GetChild(0);
+ ASSERT_EQ(history::StarredEntry::URL, child->GetType());
+ ASSERT_EQ(L"Google Business Solutions", child->GetTitle());
+ ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com/services/"));
+
+ parent = bb_model_->other_node();
+ ASSERT_EQ(2, parent->GetChildCount());
+
+ child = parent->GetChild(0);
+ ASSERT_TRUE(child->is_folder());
+ ASSERT_EQ(L"OF1", child->GetTitle());
+ ASSERT_EQ(0, child->GetChildCount());
+
+ child = parent->GetChild(1);
+ ASSERT_EQ(history::StarredEntry::URL, child->GetType());
+ ASSERT_EQ(L"About Google", child->GetTitle());
+ ASSERT_TRUE(child->GetURL() ==
+ GURL("http://www.google.com/intl/en/about.html"));
+
+ ASSERT_TRUE(bb_model_->GetNodeByURL(GURL("http://www.google.com")) != NULL);
+ }
+};
+
+// Tests migrating bookmarks from db into file. This copies an old history db
+// file containing bookmarks and make sure they are loaded correctly and
+// persisted correctly.
+TEST_F(BookmarkBarModelTestWithProfile2, MigrateFromDBToFileTest) {
+ // Copy db file over that contains starred table.
+ std::wstring old_history_path;
+ PathService::Get(chrome::DIR_TEST_DATA, &old_history_path);
+ file_util::AppendToPath(&old_history_path, L"bookmarks");
+ file_util::AppendToPath(&old_history_path, L"History_with_starred");
+ std::wstring new_history_path = profile_->GetPath();
+ file_util::Delete(new_history_path, true);
+ file_util::CreateDirectory(new_history_path);
+ file_util::AppendToPath(&new_history_path, chrome::kHistoryFilename);
+ file_util::CopyFile(old_history_path, new_history_path);
+
+ // Create the history service making sure it doesn't blow away the file we
+ // just copied.
+ profile_->CreateHistoryService(false);
+ profile_->CreateBookmarkBarModel(true);
+ BlockTillBookmarkModelLoaded();
+
+ // Make sure we loaded OK.
+ VerifyExpectedState();
+ if (HasFatalFailure())
+ return;
+
+ // Create again. This time we shouldn't load from history at all.
+ profile_->CreateBookmarkBarModel(false);
+ BlockTillBookmarkModelLoaded();
+
+ // Make sure we loaded OK.
+ VerifyExpectedState();
+ if (HasFatalFailure())
+ return;
+
+ // Recreate the history service (with a clean db). Do this just to make sure
+ // we're loading correctly from the bookmarks file.
+ profile_->CreateHistoryService(true);
+ profile_->CreateBookmarkBarModel(false);
+ BlockTillBookmarkModelLoaded();
+ VerifyExpectedState();
+}
+
+// Simple test that removes a bookmark. This test exercises the code paths in
+// History that block till bookmark bar model is loaded.
+TEST_F(BookmarkBarModelTestWithProfile2, RemoveNotification) {
+ profile_->CreateHistoryService(false);
+ profile_->CreateBookmarkBarModel(true);
+ BlockTillBookmarkModelLoaded();
+
+ // Add a URL.
+ GURL url("http://www.google.com");
+ bb_model_->SetURLStarred(url, std::wstring(), true);
+
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->AddPage(
+ url, NULL, 1, GURL(), PageTransition::TYPED,
+ HistoryService::RedirectList());
+
+ // This won't actually delete the URL, rather it'll empty out the visits.
+ // This triggers blocking on the BookmarkBarModel.
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->DeleteURL(url);
+}
diff --git a/chrome/browser/bookmarks/bookmark_codec.cc b/chrome/browser/bookmarks/bookmark_codec.cc
new file mode 100644
index 0000000..93874f3
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_codec.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2006-2008 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_codec.h"
+
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/bookmarks/bookmark_bar_model.h"
+#include "googleurl/src/gurl.h"
+
+#include "generated_resources.h"
+
+// Key names.
+static const wchar_t* kRootsKey = L"roots";
+static const wchar_t* kRootFolderNameKey = L"bookmark_bar";
+static const wchar_t* kOtherBookmarFolderNameKey = L"other";
+static const wchar_t* kVersionKey = L"version";
+static const wchar_t* kTypeKey = L"type";
+static const wchar_t* kNameKey = L"name";
+static const wchar_t* kDateAddedKey = L"date_added";
+static const wchar_t* kURLKey = L"url";
+static const wchar_t* kDateModifiedKey = L"date_modified";
+static const wchar_t* kChildrenKey = L"children";
+
+// Possible values for kTypeKey.
+static const wchar_t* kTypeURL = L"url";
+static const wchar_t* kTypeFolder = L"folder";
+
+// Current version of the file.
+static const int kCurrentVersion = 1;
+
+Value* BookmarkCodec::Encode(BookmarkBarModel* model) {
+ return Encode(model->GetBookmarkBarNode(), model->other_node());
+}
+
+Value* BookmarkCodec::Encode(BookmarkBarNode* bookmark_bar_node,
+ BookmarkBarNode* other_folder_node) {
+ DictionaryValue* roots = new DictionaryValue();
+ roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node));
+ roots->Set(kOtherBookmarFolderNameKey, EncodeNode(other_folder_node));
+
+ DictionaryValue* main = new DictionaryValue();
+ main->SetInteger(kVersionKey, kCurrentVersion);
+ main->Set(kRootsKey, roots);
+ return main;
+}
+
+bool BookmarkCodec::Decode(BookmarkBarModel* model, const Value& value) {
+ if (value.GetType() != Value::TYPE_DICTIONARY)
+ return false; // Unexpected type.
+
+ const DictionaryValue& d_value = static_cast<const DictionaryValue&>(value);
+
+ int version;
+ if (!d_value.GetInteger(kVersionKey, &version) || version != kCurrentVersion)
+ return false; // Unknown version.
+
+ Value* roots;
+ if (!d_value.Get(kRootsKey, &roots))
+ return false; // No roots.
+
+ if (roots->GetType() != Value::TYPE_DICTIONARY)
+ return false; // Invalid type for roots.
+
+ DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots);
+ Value* root_folder_value;
+ Value* other_folder_value;
+ if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) ||
+ root_folder_value->GetType() != Value::TYPE_DICTIONARY ||
+ !roots_d_value->Get(kOtherBookmarFolderNameKey, &other_folder_value) ||
+ other_folder_value->GetType() != Value::TYPE_DICTIONARY)
+ return false; // Invalid type for root folder and/or other folder.
+
+ DecodeNode(model, *static_cast<DictionaryValue*>(root_folder_value),
+ NULL, model->GetBookmarkBarNode());
+ DecodeNode(model, *static_cast<DictionaryValue*>(other_folder_value),
+ NULL, model->other_node());
+ // Need to reset the type as decoding resets the type to FOLDER. Similarly
+ // we need to reset the title as the title is persisted and restored from
+ // the file.
+ model->GetBookmarkBarNode()->type_ = history::StarredEntry::BOOKMARK_BAR;
+ model->other_node()->type_ = history::StarredEntry::OTHER;
+ model->GetBookmarkBarNode()->SetTitle(
+ l10n_util::GetString(IDS_BOOMARK_BAR_FOLDER_NAME));
+ model->other_node()->SetTitle(
+ l10n_util::GetString(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
+ return true;
+}
+
+Value* BookmarkCodec::EncodeNode(BookmarkBarNode* node) {
+ DictionaryValue* value = new DictionaryValue();
+ value->SetString(kNameKey, node->GetTitle());
+ value->SetString(kDateAddedKey,
+ Int64ToWString(node->date_added().ToInternalValue()));
+ if (node->GetType() == history::StarredEntry::URL) {
+ value->SetString(kTypeKey, kTypeURL);
+ value->SetString(kURLKey,
+ UTF8ToWide(node->GetURL().possibly_invalid_spec()));
+ } else {
+ value->SetString(kTypeKey, kTypeFolder);
+ value->SetString(kDateModifiedKey,
+ Int64ToWString(node->date_group_modified().
+ ToInternalValue()));
+
+ ListValue* child_values = new ListValue();
+ value->Set(kChildrenKey, child_values);
+ for (int i = 0; i < node->GetChildCount(); ++i)
+ child_values->Append(EncodeNode(node->GetChild(i)));
+ }
+ return value;
+}
+
+bool BookmarkCodec::DecodeChildren(BookmarkBarModel* model,
+ const ListValue& child_value_list,
+ BookmarkBarNode* parent) {
+ for (size_t i = 0; i < child_value_list.GetSize(); ++i) {
+ Value* child_value;
+ if (!child_value_list.Get(i, &child_value))
+ return false;
+
+ if (child_value->GetType() != Value::TYPE_DICTIONARY)
+ return false;
+
+ if (!DecodeNode(model, *static_cast<DictionaryValue*>(child_value), parent,
+ NULL)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool BookmarkCodec::DecodeNode(BookmarkBarModel* model,
+ const DictionaryValue& value,
+ BookmarkBarNode* parent,
+ BookmarkBarNode* node) {
+ bool created_node = (node == NULL);
+ std::wstring title;
+ if (!value.GetString(kNameKey, &title))
+ return false;
+
+ // TODO(sky): this should be more flexible. Don't hoark if we can't parse it
+ // all.
+ std::wstring date_added_string;
+ if (!value.GetString(kDateAddedKey, &date_added_string))
+ return false;
+
+ std::wstring type_string;
+ if (!value.GetString(kTypeKey, &type_string))
+ return false;
+
+ if (type_string != kTypeURL && type_string != kTypeFolder)
+ return false; // Unknown type.
+
+ if (type_string == kTypeURL) {
+ std::wstring url_string;
+ if (!value.GetString(kURLKey, &url_string))
+ return false;
+ // TODO(sky): this should ignore the node if not a valid URL.
+ if (!node)
+ node = new BookmarkBarNode(model, GURL(url_string));
+ if (parent)
+ parent->Add(parent->GetChildCount(), node);
+ node->type_ = history::StarredEntry::URL;
+ } else {
+ std::wstring last_modified_date;
+ if (!value.GetString(kDateModifiedKey, &last_modified_date))
+ return false;
+
+ Value* child_values;
+ if (!value.Get(kChildrenKey, &child_values))
+ return false;
+
+ if (child_values->GetType() != Value::TYPE_LIST)
+ return false;
+
+ if (!node)
+ node = new BookmarkBarNode(model, GURL());
+ node->type_ = history::StarredEntry::USER_GROUP;
+ node->date_group_modified_ =
+ Time::FromInternalValue(StringToInt64(last_modified_date));
+
+ if (parent)
+ parent->Add(parent->GetChildCount(), node);
+
+ if (!DecodeChildren(model, *static_cast<ListValue*>(child_values), node))
+ return false;
+ }
+
+ node->SetTitle(title);
+ node->date_added_ =
+ Time::FromInternalValue(StringToInt64(date_added_string));
+ return true;
+}
+
diff --git a/chrome/browser/bookmarks/bookmark_codec.h b/chrome/browser/bookmarks/bookmark_codec.h
new file mode 100644
index 0000000..1470f2c
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_codec.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2006-2008 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.
+
+// BookmarkCodec is responsible for encoding and decoding the BookmarkBarModel
+// into JSON values. The encoded values are written to disk via the
+// BookmarkService.
+
+#ifndef CHROME_BROWSER_BOOKMARKS_BOOKMARK_CODEC_H_
+#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_CODEC_H_
+
+#include "base/basictypes.h"
+
+class BookmarkBarModel;
+class BookmarkBarNode;
+class DictionaryValue;
+class ListValue;
+class Value;
+
+// BookmarkCodec is responsible for encoding/decoding bookmarks into JSON
+// values. BookmarkCodec is used by BookmarkService.
+
+class BookmarkCodec {
+ public:
+ BookmarkCodec() {}
+
+ // Encodes the model to a JSON value. It's up to the caller to delete the
+ // returned object. This is invoked to encode the contents of the bookmark bar
+ // model and is currently a convenience to invoking Encode that takes the
+ // bookmark bar node and other folder node.
+ Value* Encode(BookmarkBarModel* model);
+
+ // Encodes the bookmark bar and other folders returning the JSON value. It's
+ // up to the caller to delete the returned object.
+ // This method is public for use by StarredURLDatabase in migrating the
+ // bookmarks out of the database.
+ Value* Encode(BookmarkBarNode* bookmark_bar_node,
+ BookmarkBarNode* other_folder_node);
+
+ // Decodes the previously encoded value to the specified model. Returns true
+ // on success, false otherwise. If there is an error (such as unexpected
+ // version) all children are removed from the bookmark bar and other folder
+ // nodes.
+ bool Decode(BookmarkBarModel* model, const Value& value);
+
+ private:
+ // Encodes node and all its children into a Value object and returns it.
+ // The caller takes ownership of the returned object.
+ Value* EncodeNode(BookmarkBarNode* node);
+
+ // Decodes the children of the specified node. Returns true on success.
+ bool DecodeChildren(BookmarkBarModel* model,
+ const ListValue& child_value_list,
+ BookmarkBarNode* parent);
+
+ // Decodes the supplied node from the supplied value. Child nodes are
+ // created appropriately by way of DecodeChildren. If node is NULL a new
+ // node is created and added to parent, otherwise node is used.
+ bool DecodeNode(BookmarkBarModel* model,
+ const DictionaryValue& value,
+ BookmarkBarNode* parent,
+ BookmarkBarNode* node);
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkCodec);
+};
+
+#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_CODEC_H_
diff --git a/chrome/browser/bookmarks/bookmark_drag_data.cc b/chrome/browser/bookmarks/bookmark_drag_data.cc
new file mode 100644
index 0000000..61c9ccc
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_drag_data.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2006-2008 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_drag_data.h"
+
+#include "base/pickle.h"
+#include "chrome/browser/bookmarks/bookmark_bar_model.h"
+#include "chrome/common/os_exchange_data.h"
+
+static CLIPFORMAT clipboard_format = 0;
+
+static void RegisterFormat() {
+ if (clipboard_format == 0) {
+ clipboard_format = RegisterClipboardFormat(L"chrome/x-bookmark-entry");
+ DCHECK(clipboard_format);
+ }
+}
+
+BookmarkDragData::BookmarkDragData() : is_url(false), is_valid(false) {
+}
+
+BookmarkDragData::BookmarkDragData(BookmarkBarNode* node)
+ : is_url(node->GetType() == history::StarredEntry::URL),
+ url(node->GetURL()),
+ title(node->GetTitle()),
+ is_valid(true),
+ id_(node->id()) {
+ if (!is_url)
+ AddChildren(node);
+}
+
+void BookmarkDragData::Write(OSExchangeData* data) const {
+ RegisterFormat();
+
+ DCHECK(data);
+
+ if (is_url) {
+ data->SetURL(url, title);
+ }
+ Pickle data_pickle;
+ WriteToPickle(&data_pickle);
+ data->SetPickledData(clipboard_format, data_pickle);
+}
+
+bool BookmarkDragData::Read(const OSExchangeData& data) {
+ RegisterFormat();
+
+ is_valid = data.GetURLAndTitle(&url, &title) && url.is_valid();
+ is_url = is_valid;
+ profile_id.clear();
+
+ if (data.HasFormat(clipboard_format)) {
+ Pickle drag_data_pickle;
+ if (data.GetPickledData(clipboard_format, &drag_data_pickle)) {
+ void* data_iterator = NULL;
+ if (ReadFromPickle(&drag_data_pickle, &data_iterator)) {
+ is_valid = true;
+ }
+ }
+ }
+ return is_valid;
+}
+
+BookmarkBarNode* BookmarkDragData::GetNode(BookmarkBarModel* model) const {
+ DCHECK(!is_url && id_ && is_valid);
+ return model->GetNodeByID(id_);
+}
+
+void BookmarkDragData::WriteToPickle(Pickle* pickle) const {
+ pickle->WriteBool(is_url);
+ pickle->WriteWString(profile_id);
+ pickle->WriteString(url.spec());
+ pickle->WriteWString(title);
+ if (!is_url) {
+ pickle->WriteInt(id_);
+ pickle->WriteInt(static_cast<int>(children.size()));
+ for (std::vector<BookmarkDragData>::const_iterator i = children.begin();
+ i != children.end(); ++i) {
+ i->WriteToPickle(pickle);
+ }
+ }
+}
+
+bool BookmarkDragData::ReadFromPickle(Pickle* pickle, void** iterator) {
+ std::string url_spec;
+ is_valid = false;
+ if (!pickle->ReadBool(iterator, &is_url) ||
+ !pickle->ReadWString(iterator, &profile_id) ||
+ !pickle->ReadString(iterator, &url_spec) ||
+ !pickle->ReadWString(iterator, &title)) {
+ return false;
+ }
+ url = GURL(url_spec);
+ if (!is_url) {
+ id_ = 0;
+ children.clear();
+ if (!pickle->ReadInt(iterator, &id_))
+ return false;
+ int children_count;
+ if (!pickle->ReadInt(iterator, &children_count))
+ return false;
+ children.resize(children_count);
+ for (std::vector<BookmarkDragData>::iterator i = children.begin();
+ i != children.end(); ++i) {
+ if (!i->ReadFromPickle(pickle, iterator))
+ return false;
+ }
+ }
+ is_valid = true;
+ return true;
+}
+
+void BookmarkDragData::AddChildren(BookmarkBarNode* node) {
+ for (int i = 0, max = node->GetChildCount(); i < max; ++i)
+ children.push_back(BookmarkDragData(node->GetChild(i)));
+}
+
diff --git a/chrome/browser/bookmarks/bookmark_drag_data.h b/chrome/browser/bookmarks/bookmark_drag_data.h
new file mode 100644
index 0000000..fd967eb
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_drag_data.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2006-2008 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_DRAG_DATA_
+#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_DRAG_DATA_
+
+#include <vector>
+
+#include "chrome/browser/history/history.h"
+#include "googleurl/src/gurl.h"
+
+class BookmarkBarModel;
+class BookmarkBarNode;
+class OSExchangeData;
+class Pickle;
+
+// BookmarkDragData is used by the bookmark bar to represent a dragged
+// URL or starred group on the clipboard during drag and drop.
+//
+// Typical usage when writing data for a drag is:
+// BookmarkDragData data(node_user_is_dragging);
+// data.profile_id = profile_id;
+// data.Write(os_exchange_data_for_drag);
+//
+// Typical usage to read is:
+// BookmarkDragData data;
+// if (data.Read(os_exchange_data))
+// // data is valid
+
+struct BookmarkDragData {
+ BookmarkDragData();
+
+ // Created a BookmarkDragData populated from node.
+ explicit BookmarkDragData(BookmarkBarNode* node);
+
+ // Writes this BookmarkDragData to data. If BookmarkDragData is a URL,
+ // this writes out the URL and URL title clipboard data as well.
+ void Write(OSExchangeData* data) const;
+
+ // Restores this data from the clipboard, returning true on success.
+ bool Read(const OSExchangeData& data);
+
+ // Returns the node represented by this drag data from root. If the
+ // path can not be found, NULL is returned.
+ //
+ // This is only valid for groups.
+ BookmarkBarNode* BookmarkDragData::GetNode(BookmarkBarModel* model) const;
+
+ // If true, this entry represents a StarredEntry of type URL.
+ bool is_url;
+
+ // ID of the profile we originated from.
+ std::wstring profile_id;
+
+ // The URL, only valid if is_url is true.
+ GURL url;
+
+ // Title of the entry
+ std::wstring title;
+
+ // Children, only used for non-URL nodes.
+ std::vector<BookmarkDragData> children;
+
+ // If true our data is valid.
+ bool is_valid;
+
+ private:
+ // Writes the data to a Pickle.
+ void WriteToPickle(Pickle* pickle) const;
+
+ bool ReadFromPickle(Pickle* pickle, void** iterator);
+
+ // Adds to children an entry for each child of node.
+ void AddChildren(BookmarkBarNode* node);
+
+ // ID (node->id()) of the node this BookmarkDragData was created from.
+ int id_;
+};
+
+#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_DRAG_DATA_
diff --git a/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc b/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc
new file mode 100644
index 0000000..4ebe7b0
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2006-2008 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 "base/scoped_ptr.h"
+#include "chrome/browser/bookmarks/bookmark_bar_model.h"
+#include "chrome/browser/bookmarks/bookmark_drag_data.h"
+#include "chrome/common/os_exchange_data.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef testing::Test BookmarkDragDataTest;
+
+TEST_F(BookmarkDragDataTest, InitialState) {
+ BookmarkDragData data;
+ EXPECT_FALSE(data.is_valid);
+}
+
+TEST_F(BookmarkDragDataTest, BogusRead) {
+ scoped_refptr<OSExchangeData> data(new OSExchangeData());
+ BookmarkDragData drag_data;
+ drag_data.is_valid = true;
+ EXPECT_FALSE(drag_data.Read(data.get()));
+ EXPECT_FALSE(drag_data.is_valid);
+}
+
+TEST_F(BookmarkDragDataTest, URL) {
+ BookmarkBarModel model(NULL);
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ GURL url(GURL("http://foo.com"));
+ const std::wstring profile_id(L"blah");
+ const std::wstring title(L"blah");
+ BookmarkBarNode* node = model.AddURL(root, 0, title, url);
+ BookmarkDragData drag_data(node);
+ drag_data.profile_id = profile_id;
+ EXPECT_TRUE(drag_data.url == url);
+ EXPECT_EQ(title, drag_data.title);
+ EXPECT_TRUE(drag_data.is_url);
+
+ scoped_refptr<OSExchangeData> data(new OSExchangeData());
+ drag_data.Write(data.get());
+
+ // Now read the data back in.
+ scoped_refptr<OSExchangeData> data2(new OSExchangeData(data.get()));
+ BookmarkDragData read_data;
+ EXPECT_TRUE(read_data.Read(*data2));
+ EXPECT_TRUE(read_data.url == url);
+ EXPECT_EQ(title, read_data.title);
+ EXPECT_TRUE(read_data.is_valid);
+ EXPECT_TRUE(read_data.is_url);
+
+ // Writing should also put the URL and title on the clipboard.
+ GURL read_url;
+ std::wstring read_title;
+ EXPECT_TRUE(data2->GetURLAndTitle(&read_url, &read_title));
+ EXPECT_TRUE(read_url == url);
+ EXPECT_EQ(title, read_title);
+}
+
+TEST_F(BookmarkDragDataTest, Group) {
+ BookmarkBarModel model(NULL);
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ BookmarkBarNode* g1 = model.AddGroup(root, 0, L"g1");
+ BookmarkBarNode* g11 = model.AddGroup(g1, 0, L"g11");
+ BookmarkBarNode* g12 = model.AddGroup(g1, 0, L"g12");
+
+ BookmarkDragData drag_data(g12);
+ const std::wstring profile_id(L"blah");
+ drag_data.profile_id = profile_id;
+ EXPECT_EQ(g12->GetTitle(), drag_data.title);
+ EXPECT_FALSE(drag_data.is_url);
+
+ scoped_refptr<OSExchangeData> data(new OSExchangeData());
+ drag_data.Write(data.get());
+
+ // Now read the data back in.
+ scoped_refptr<OSExchangeData> data2(new OSExchangeData(data.get()));
+ BookmarkDragData read_data;
+ EXPECT_TRUE(read_data.Read(*data2));
+ EXPECT_EQ(g12->GetTitle(), read_data.title);
+ EXPECT_EQ(profile_id, read_data.profile_id);
+ EXPECT_TRUE(read_data.is_valid);
+ EXPECT_FALSE(read_data.is_url);
+
+ BookmarkBarNode* r_g12 = read_data.GetNode(&model);
+ EXPECT_TRUE(g12 == r_g12);
+}
+
+TEST_F(BookmarkDragDataTest, GroupWithChild) {
+ BookmarkBarModel model(NULL);
+ BookmarkBarNode* root = model.GetBookmarkBarNode();
+ BookmarkBarNode* group = model.AddGroup(root, 0, L"g1");
+
+ GURL url(GURL("http://foo.com"));
+ const std::wstring profile_id(L"blah");
+ const std::wstring title(L"blah2");
+
+ model.AddURL(group, 0, title, url);
+
+ BookmarkDragData drag_data(group);
+ drag_data.profile_id = profile_id;
+
+ scoped_refptr<OSExchangeData> data(new OSExchangeData());
+ drag_data.Write(data.get());
+
+ // Now read the data back in.
+ scoped_refptr<OSExchangeData> data2(new OSExchangeData(data.get()));
+ BookmarkDragData read_data;
+ EXPECT_TRUE(read_data.Read(*data2));
+
+ EXPECT_EQ(1, read_data.children.size());
+ EXPECT_TRUE(read_data.children[0].is_valid);
+ EXPECT_TRUE(read_data.children[0].is_url);
+ EXPECT_EQ(title, read_data.children[0].title);
+ EXPECT_TRUE(url == read_data.children[0].url);
+ EXPECT_TRUE(read_data.children[0].is_url);
+
+ BookmarkBarNode* r_group = read_data.GetNode(&model);
+ EXPECT_TRUE(group == r_group);
+}
diff --git a/chrome/browser/bookmarks/bookmark_storage.cc b/chrome/browser/bookmarks/bookmark_storage.cc
new file mode 100644
index 0000000..712b573
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_storage.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2006-2008 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_storage.h"
+
+#include "base/file_util.h"
+#include "base/json_writer.h"
+#include "base/message_loop.h"
+#include "chrome/browser/bookmarks/bookmark_bar_model.h"
+#include "chrome/browser/bookmarks/bookmark_codec.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/json_value_serializer.h"
+
+namespace {
+
+// Extension used for backup files (copy of main file created during startup).
+const wchar_t* const kBackupExtension = L"bak";
+
+// Extension for the temporary file. We write to the temp file than move to
+// kBookmarksFileName.
+const wchar_t* const kTmpExtension = L"tmp";
+
+// How often we save.
+const int kSaveDelayMS = 2500;
+
+} // namespace
+
+// BookmarkStorage -------------------------------------------------------------
+
+BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkBarModel* model)
+ : model_(model),
+#pragma warning(suppress: 4355) // Okay to pass "this" here.
+ save_factory_(this),
+ backend_thread_(g_browser_process->file_thread()) {
+ std::wstring path = profile->GetPath();
+ file_util::AppendToPath(&path, chrome::kBookmarksFileName);
+ std::wstring tmp_history_path = profile->GetPath();
+ file_util::AppendToPath(&tmp_history_path, chrome::kHistoryBookmarksFileName);
+ backend_ = new BookmarkStorageBackend(path, tmp_history_path);
+}
+
+void BookmarkStorage::LoadBookmarks(bool load_from_history) {
+ if (!backend_thread()) {
+ backend_->Read(scoped_refptr<BookmarkStorage>(this), NULL,
+ load_from_history);
+ } else {
+ backend_thread()->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Read,
+ scoped_refptr<BookmarkStorage>(this),
+ MessageLoop::current(), load_from_history));
+ }
+}
+
+void BookmarkStorage::ScheduleSave() {
+ if (!backend_thread()) {
+ SaveNow();
+ } else if (save_factory_.empty()) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, save_factory_.NewRunnableMethod(&BookmarkStorage::SaveNow),
+ kSaveDelayMS);
+ }
+}
+
+void BookmarkStorage::BookmarkModelDeleted() {
+ if (!save_factory_.empty()) {
+ // There's a pending save. We need to save now as otherwise by the time
+ // SaveNow is invoked the model is gone.
+ save_factory_.RevokeAll();
+ SaveNow();
+ }
+ model_ = NULL;
+}
+
+void BookmarkStorage::LoadedBookmarks(Value* root_value,
+ bool bookmark_file_exists,
+ bool loaded_from_history) {
+ scoped_ptr<Value> value_ref(root_value);
+
+ if (model_) {
+ if (root_value) {
+ BookmarkCodec codec;
+ codec.Decode(model_, *root_value);
+ }
+ model_->OnBookmarkStorageLoadedBookmarks(bookmark_file_exists,
+ loaded_from_history);
+ }
+}
+
+void BookmarkStorage::SaveNow() {
+ if (!model_ || !model_->IsLoaded()) {
+ // We should only get here if we have a valid model and it's finished
+ // loading.
+ NOTREACHED();
+ return;
+ }
+
+ BookmarkCodec codec;
+ Value* value = codec.Encode(model_);
+ // The backend deletes value in write.
+ if (!backend_thread()) {
+ backend_->Write(value);
+ } else {
+ backend_thread()->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Write,
+ value));
+ }
+}
+
+// BookmarkStorageBackend ------------------------------------------------------
+
+BookmarkStorageBackend::BookmarkStorageBackend(
+ const std::wstring& path,
+ const std::wstring& tmp_history_path)
+ : path_(path),
+ tmp_history_path_(tmp_history_path) {
+ // Make a backup of the current file.
+ std::wstring backup_path = path;
+ file_util::ReplaceExtension(&backup_path, kBackupExtension);
+ file_util::CopyFile(path, backup_path);
+}
+
+void BookmarkStorageBackend::Write(Value* value) {
+ DCHECK(value);
+
+ // We own Value.
+ scoped_ptr<Value> value_ref(value);
+
+ std::string content;
+ JSONWriter::Write(value, true, &content);
+
+ // Write to a temp file, then rename.
+ std::wstring tmp_file = path_;
+ file_util::ReplaceExtension(&tmp_file, kTmpExtension);
+
+ int bytes_written = file_util::WriteFile(tmp_file, content.c_str(),
+ static_cast<int>(content.length()));
+ if (bytes_written != -1) {
+ if (!MoveFileEx(tmp_file.c_str(), path_.c_str(),
+ MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) {
+ // Rename failed. Try again on the off chance someone has locked either
+ // file and hope we're successful the second time through.
+ BOOL move_result =
+ MoveFileEx(tmp_file.c_str(), path_.c_str(),
+ MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
+ DCHECK(move_result);
+ }
+ // Nuke the history file so that we don't attempt to load from it again.
+ file_util::Delete(tmp_history_path_, false);
+ }
+}
+
+void BookmarkStorageBackend::Read(scoped_refptr<BookmarkStorage> service,
+ MessageLoop* message_loop,
+ bool load_from_history) {
+ const std::wstring& path = load_from_history ? tmp_history_path_ : path_;
+ bool bookmark_file_exists = file_util::PathExists(path);
+ Value* root = NULL;
+ if (bookmark_file_exists) {
+ JSONFileValueSerializer serializer(path);
+ serializer.Deserialize(&root);
+ }
+
+ // BookmarkStorage takes ownership of root.
+ if (message_loop) {
+ message_loop->PostTask(FROM_HERE, NewRunnableMethod(
+ service.get(), &BookmarkStorage::LoadedBookmarks, root,
+ bookmark_file_exists, load_from_history));
+ } else {
+ service->LoadedBookmarks(root, bookmark_file_exists, load_from_history);
+ }
+}
diff --git a/chrome/browser/bookmarks/bookmark_storage.h b/chrome/browser/bookmarks/bookmark_storage.h
new file mode 100644
index 0000000..cf2c3a9
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_storage.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2006-2008 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_STORAGE_H_
+#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_STORAGE_H_
+
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "chrome/browser/browser_process.h"
+
+class BookmarkBarModel;
+class Profile;
+class Value;
+
+// BookmarkStorage handles reading/write the bookmark bar model. The
+// BookmarkBarModel uses the BookmarkStorage to load bookmarks from
+// disk, as well as notifying the BookmarkStorage every time the model
+// changes.
+//
+// Internally BookmarkStorage uses BookmarkCodec to do the actual read/write.
+
+class BookmarkStorage : public base::RefCountedThreadSafe<BookmarkStorage> {
+ friend class BookmarkStorageBackend;
+
+ public:
+ // Creates a BookmarkStorage for the specified model
+ BookmarkStorage(Profile* profile, BookmarkBarModel* model);
+
+ // Loads the bookmarks into the model, notifying the model when done. If
+ // load_from_history is true, the bookmarks are loaded from the file written
+ // by history (StarredURLDatabase).
+ void LoadBookmarks(bool load_from_history);
+
+ // Schedules saving the bookmark bar model to disk.
+ void ScheduleSave();
+
+ // Notification the bookmark bar model is going to be deleted. If there is
+ // a pending save, it is saved immediately.
+ void BookmarkModelDeleted();
+
+ private:
+ // Callback from backend with the results of the bookmark file.
+ void LoadedBookmarks(Value* root_value,
+ bool bookmark_file_exists,
+ bool loaded_from_history);
+
+ // Schedules a save on the backend thread.
+ void SaveNow();
+
+ // Returns the thread the backend is run on.
+ base::Thread* backend_thread() const { return backend_thread_; }
+
+ // The model. The model is NULL once BookmarkModelDeleted has been invoked.
+ BookmarkBarModel* model_;
+
+ // Used to delay saves.
+ ScopedRunnableMethodFactory<BookmarkStorage> save_factory_;
+
+ // The backend handles actual reading/writing to disk.
+ scoped_refptr<BookmarkStorageBackend> backend_;
+
+ // Thread read/writing is run on. This comes from the profile, and is null
+ // during testing.
+ base::Thread* backend_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkStorage);
+};
+
+// Used to save the bookmarks on the file thread.
+class BookmarkStorageBackend :
+ public base::RefCountedThreadSafe<BookmarkStorageBackend> {
+ public:
+ explicit BookmarkStorageBackend(const std::wstring& path,
+ const std::wstring& tmp_histor_path);
+
+ // Writes the specified value to disk. This takes ownership of |value| and
+ // deletes it when done.
+ void Write(Value* value);
+
+ // Reads the bookmarks from kBookmarksFileName. Notifies |service| with
+ // the results on the specified MessageLoop.
+ void Read(scoped_refptr<BookmarkStorage> service,
+ MessageLoop* message_loop,
+ bool load_from_history);
+
+ private:
+ // Path we read/write to.
+ const std::wstring path_;
+
+ // Path bookmarks are read from if asked to load from history file.
+ const std::wstring tmp_history_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkStorageBackend);
+};
+
+#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_STORAGE_H_