| Index: chrome/browser/sync/glue/bookmark_model_associator.cc
|
| diff --git a/chrome/browser/sync/glue/bookmark_model_associator.cc b/chrome/browser/sync/glue/bookmark_model_associator.cc
|
| index e8f561967e92a994dd8355ca5f75bb9001516398..29325970137f40f063c614c2d672c5b205161a10 100644
|
| --- a/chrome/browser/sync/glue/bookmark_model_associator.cc
|
| +++ b/chrome/browser/sync/glue/bookmark_model_associator.cc
|
| @@ -76,21 +76,11 @@
|
| explicit BookmarkNodeFinder(const BookmarkNode* parent_node);
|
|
|
| // Finds the bookmark node that matches the given url, title and folder
|
| - // attribute. Returns the matching node if one exists; NULL otherwise.
|
| - // If there are multiple matches then a node with ID matching |preferred_id|
|
| - // is returned; otherwise the first matching node is returned.
|
| - // If a matching node is found, it's removed for further matches.
|
| + // attribute. Returns the matching node if one exists; NULL otherwise. If a
|
| + // matching node is found, it's removed for further matches.
|
| const BookmarkNode* FindBookmarkNode(const GURL& url,
|
| const std::string& title,
|
| - bool is_folder,
|
| - int64 preferred_id);
|
| -
|
| - // Returns true if |bookmark_node| matches the specified |url|,
|
| - // |title|, and |is_folder| flags.
|
| - static bool NodeMatches(const BookmarkNode* bookmark_node,
|
| - const GURL& url,
|
| - const std::string& title,
|
| - bool is_folder);
|
| + bool is_folder);
|
|
|
| private:
|
| // Maps bookmark node titles to instances, duplicates allowed.
|
| @@ -103,8 +93,8 @@
|
|
|
| // Converts and truncates bookmark titles in the form sync does internally
|
| // to avoid mismatches due to sync munging titles.
|
| - static void ConvertTitleToSyncInternalFormat(const std::string& input,
|
| - std::string* output);
|
| + void ConvertTitleToSyncInternalFormat(
|
| + const std::string& input, std::string* output);
|
|
|
| const BookmarkNode* parent_node_;
|
| BookmarkNodeMap child_nodes_;
|
| @@ -142,63 +132,28 @@
|
| }
|
|
|
| const BookmarkNode* BookmarkNodeFinder::FindBookmarkNode(
|
| - const GURL& url,
|
| - const std::string& title,
|
| - bool is_folder,
|
| - int64 preferred_id) {
|
| - const BookmarkNode* match = nullptr;
|
| -
|
| + const GURL& url, const std::string& title, bool is_folder) {
|
| // First lookup a range of bookmarks with the same title.
|
| std::string adjusted_title;
|
| ConvertTitleToSyncInternalFormat(title, &adjusted_title);
|
| BookmarkNodeRange range = child_nodes_.equal_range(adjusted_title);
|
| - BookmarkNodeMap::iterator match_iter = range.second;
|
| for (BookmarkNodeMap::iterator iter = range.first;
|
| iter != range.second;
|
| ++iter) {
|
| +
|
| // Then within the range match the node by the folder bit
|
| // and the url.
|
| const BookmarkNode* node = iter->second;
|
| if (is_folder == node->is_folder() && url == node->url()) {
|
| - if (node->id() == preferred_id || preferred_id == 0) {
|
| - // Preferred match - use this node.
|
| - match = node;
|
| - match_iter = iter;
|
| - break;
|
| - } else if (match == nullptr) {
|
| - // First match - continue iterating.
|
| - match = node;
|
| - match_iter = iter;
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (match_iter != range.second) {
|
| - // Remove the matched node so we don't match with it again.
|
| - child_nodes_.erase(match_iter);
|
| - }
|
| -
|
| - return match;
|
| -}
|
| -
|
| -/* static */
|
| -bool BookmarkNodeFinder::NodeMatches(const BookmarkNode* bookmark_node,
|
| - const GURL& url,
|
| - const std::string& title,
|
| - bool is_folder) {
|
| - if (url != bookmark_node->url() || is_folder != bookmark_node->is_folder()) {
|
| - return false;
|
| - }
|
| -
|
| - // The title passed to this method comes from a sync directory entry.
|
| - // The following two lines are needed to make the native bookmark title
|
| - // comparable. The same conversion is used in BookmarkNodeFinder constructor.
|
| - std::string bookmark_title = base::UTF16ToUTF8(bookmark_node->GetTitle());
|
| - ConvertTitleToSyncInternalFormat(bookmark_title, &bookmark_title);
|
| - return title == bookmark_title;
|
| -}
|
| -
|
| -/* static */
|
| + // Remove the matched node so we don't match with it again.
|
| + child_nodes_.erase(iter);
|
| + return node;
|
| + }
|
| + }
|
| +
|
| + return NULL;
|
| +}
|
| +
|
| void BookmarkNodeFinder::ConvertTitleToSyncInternalFormat(
|
| const std::string& input, std::string* output) {
|
| syncer::SyncAPINameToServerName(input, output);
|
| @@ -391,6 +346,27 @@
|
| return true;
|
| }
|
|
|
| +bool BookmarkModelAssociator::NodesMatch(
|
| + const BookmarkNode* bookmark,
|
| + const syncer::BaseNode* sync_node) const {
|
| + std::string truncated_title = base::UTF16ToUTF8(bookmark->GetTitle());
|
| + base::TruncateUTF8ToByteSize(truncated_title,
|
| + kTitleLimitBytes,
|
| + &truncated_title);
|
| + if (truncated_title != sync_node->GetTitle())
|
| + return false;
|
| + if (bookmark->is_folder() != sync_node->GetIsFolder())
|
| + return false;
|
| + if (bookmark->is_url()) {
|
| + if (bookmark->url() != GURL(sync_node->GetBookmarkSpecifics().url()))
|
| + return false;
|
| + }
|
| + // Don't compare favicons here, because they are not really
|
| + // user-updated and we don't have versioning information -- a site changing
|
| + // its favicon shouldn't result in a bookmark mismatch.
|
| + return true;
|
| +}
|
| +
|
| bool BookmarkModelAssociator::AssociateTaggedPermanentNode(
|
| const BookmarkNode* permanent_node, const std::string&tag) {
|
| // Do nothing if |permanent_node| is already initialized and associated.
|
| @@ -448,10 +424,11 @@
|
| // * When all children sync nodes are done, add the extra children bookmark
|
| // nodes to the sync parent node.
|
| //
|
| - // The best match algorithm uses folder title or bookmark title/url to
|
| - // perform the primary match. If there are multiple match candidates it
|
| - // selects the preferred one based on sync node external ID match to the
|
| - // bookmark folder ID.
|
| + // This algorithm will do a good job of merging when folder names are a good
|
| + // indicator of the two folders being the same. It will handle reordering and
|
| + // new node addition very well (without creating duplicates).
|
| + // This algorithm will not do well if the folder name has changes but the
|
| + // children under them are all the same.
|
|
|
| DCHECK(bookmark_model_->loaded());
|
|
|
| @@ -515,9 +492,6 @@
|
| bookmark_model_->root_node()->GetTotalNodeCount());
|
|
|
| // Remove obsolete bookmarks according to sync delete journal.
|
| - // TODO (stanisc): crbug.com/456876: rewrite this to avoid a separate
|
| - // traversal and instead perform deletes at the end of the loop below where
|
| - // the unmatched bookmark nodes are created as sync nodes.
|
| local_merge_result->set_num_items_deleted(
|
| ApplyDeletesFromSyncJournal(&trans));
|
|
|
| @@ -560,10 +534,11 @@
|
| model_type());
|
| }
|
|
|
| - const BookmarkNode* child_node = node_finder.FindBookmarkNode(
|
| + const BookmarkNode* child_node = NULL;
|
| + child_node = node_finder.FindBookmarkNode(
|
| GURL(sync_child_node.GetBookmarkSpecifics().url()),
|
| - sync_child_node.GetTitle(), sync_child_node.GetIsFolder(),
|
| - sync_child_node.GetExternalId());
|
| + sync_child_node.GetTitle(),
|
| + sync_child_node.GetIsFolder());
|
| if (child_node) {
|
| Associate(child_node, sync_child_id);
|
|
|
| @@ -641,14 +616,6 @@
|
| return 0;
|
| size_t num_journals_unmatched = bk_delete_journals.size();
|
|
|
| - // Make a set of all external IDs in the delete journal,
|
| - // ignore entries with unset external IDs.
|
| - std::set<int64> journaled_external_ids;
|
| - for (size_t i = 0; i < num_journals_unmatched; i++) {
|
| - if (bk_delete_journals[i].external_id != 0)
|
| - journaled_external_ids.insert(bk_delete_journals[i].external_id);
|
| - }
|
| -
|
| // Check bookmark model from top to bottom.
|
| std::stack<const BookmarkNode*> dfs_stack;
|
| dfs_stack.push(bookmark_model_->bookmark_bar_node());
|
| @@ -663,53 +630,39 @@
|
| // bookmarks are removed in first pass, recheck the folders in reverse order
|
| // to remove empty ones.
|
| FolderInfoList folders_matched;
|
| - while (!dfs_stack.empty() && num_journals_unmatched > 0) {
|
| + while (!dfs_stack.empty()) {
|
| const BookmarkNode* parent = dfs_stack.top();
|
| dfs_stack.pop();
|
| - DCHECK(parent->is_folder());
|
| -
|
| - // Enumerate folder children in reverse order to make it easier to remove
|
| - // bookmarks matching entries in the delete journal.
|
| - for (int child_index = parent->child_count() - 1;
|
| - child_index >= 0 && num_journals_unmatched > 0; --child_index) {
|
| - const BookmarkNode* child = parent->GetChild(child_index);
|
| - if (child->is_folder())
|
| - dfs_stack.push(child);
|
| -
|
| - if (journaled_external_ids.find(child->id()) ==
|
| - journaled_external_ids.end()) {
|
| - // Skip bookmark node which id is not in the set of external IDs.
|
| - continue;
|
| +
|
| + BookmarkNodeFinder finder(parent);
|
| + // Iterate through journals from back to front. Remove matched journal by
|
| + // moving an unmatched journal at the tail to its position so that we can
|
| + // read unmatched journals off the head in next loop.
|
| + for (int i = num_journals_unmatched - 1; i >= 0; --i) {
|
| + const BookmarkNode* child = finder.FindBookmarkNode(
|
| + GURL(bk_delete_journals[i].specifics.bookmark().url()),
|
| + bk_delete_journals[i].specifics.bookmark().title(),
|
| + bk_delete_journals[i].is_folder);
|
| + if (child) {
|
| + if (child->is_folder()) {
|
| + // Remember matched folder without removing and delete only empty
|
| + // ones later.
|
| + folders_matched.push_back(FolderInfo(child, parent,
|
| + bk_delete_journals[i].id));
|
| + } else {
|
| + bookmark_model_->Remove(parent, parent->GetIndexOf(child));
|
| + ++num_bookmark_deleted;
|
| + }
|
| + // Move unmatched journal here and decrement counter.
|
| + bk_delete_journals[i] = bk_delete_journals[--num_journals_unmatched];
|
| }
|
| -
|
| - // Iterate through the journal entries from back to front. Remove matched
|
| - // journal by moving an unmatched entry at the tail to the matched
|
| - // position so that we can read unmatched entries off the head in next
|
| - // loop.
|
| - for (int journal_index = num_journals_unmatched - 1; journal_index >= 0;
|
| - --journal_index) {
|
| - const syncer::BookmarkDeleteJournal& delete_entry =
|
| - bk_delete_journals[journal_index];
|
| - if (child->id() == delete_entry.external_id &&
|
| - BookmarkNodeFinder::NodeMatches(
|
| - child, GURL(delete_entry.specifics.bookmark().url()),
|
| - delete_entry.specifics.bookmark().title(),
|
| - delete_entry.is_folder)) {
|
| - if (child->is_folder()) {
|
| - // Remember matched folder without removing and delete only empty
|
| - // ones later.
|
| - folders_matched.push_back(
|
| - FolderInfo(child, parent, delete_entry.id));
|
| - } else {
|
| - bookmark_model_->Remove(parent, child_index);
|
| - ++num_bookmark_deleted;
|
| - }
|
| - // Move unmatched journal here and decrement counter.
|
| - bk_delete_journals[journal_index] =
|
| - bk_delete_journals[--num_journals_unmatched];
|
| - break;
|
| - }
|
| - }
|
| + }
|
| + if (num_journals_unmatched == 0)
|
| + break;
|
| +
|
| + for (int i = 0; i < parent->child_count(); ++i) {
|
| + if (parent->GetChild(i)->is_folder())
|
| + dfs_stack.push(parent->GetChild(i));
|
| }
|
| }
|
|
|
|
|