| 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 5029a9883805102f8fe312a3e9676d5ce76a82e0..9f0fbe2a73349cde346de668928d25e7b5a0d68b 100644
|
| --- a/chrome/browser/sync/glue/bookmark_model_associator.cc
|
| +++ b/chrome/browser/sync/glue/bookmark_model_associator.cc
|
| @@ -18,6 +18,7 @@
|
| #include "chrome/browser/sync/glue/bookmark_change_processor.h"
|
| #include "content/public/browser/browser_thread.h"
|
| #include "sync/api/sync_error.h"
|
| +#include "sync/internal_api/public/delete_journal.h"
|
| #include "sync/internal_api/public/read_node.h"
|
| #include "sync/internal_api/public/read_transaction.h"
|
| #include "sync/internal_api/public/write_node.h"
|
| @@ -84,10 +85,12 @@ class BookmarkNodeFinder {
|
| // Creates an instance with the given parent bookmark node.
|
| explicit BookmarkNodeFinder(const BookmarkNode* parent_node);
|
|
|
| - // Finds best matching node for the given sync node.
|
| - // 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 syncer::BaseNode& sync_node);
|
| + // Finds the bookmark node that matches the given url, title and folder
|
| + // 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);
|
|
|
| private:
|
| typedef std::multiset<const BookmarkNode*, BookmarkComparer> BookmarkNodesSet;
|
| @@ -123,11 +126,11 @@ BookmarkNodeFinder::BookmarkNodeFinder(const BookmarkNode* parent_node)
|
| }
|
|
|
| const BookmarkNode* BookmarkNodeFinder::FindBookmarkNode(
|
| - const syncer::BaseNode& sync_node) {
|
| - // Create a bookmark node from the given sync node.
|
| - BookmarkNode temp_node(GURL(sync_node.GetBookmarkSpecifics().url()));
|
| - temp_node.SetTitle(UTF8ToUTF16(sync_node.GetTitle()));
|
| - if (sync_node.GetIsFolder())
|
| + const GURL& url, const std::string& title, bool is_folder) {
|
| + // Create a bookmark node from the given bookmark attributes.
|
| + BookmarkNode temp_node(url);
|
| + temp_node.SetTitle(UTF8ToUTF16(title));
|
| + if (is_folder)
|
| temp_node.set_type(BookmarkNode::FOLDER);
|
| else
|
| temp_node.set_type(BookmarkNode::URL);
|
| @@ -447,6 +450,10 @@ syncer::SyncError BookmarkModelAssociator::BuildAssociations(
|
| local_merge_result->set_num_items_before_association(
|
| bookmark_model_->root_node()->GetTotalNodeCount());
|
|
|
| + // Remove obsolete bookmarks according to sync delete journal.
|
| + local_merge_result->set_num_items_deleted(
|
| + ApplyDeletesFromSyncJournal(&trans));
|
| +
|
| while (!dfs_stack.empty()) {
|
| int64 sync_parent_id = dfs_stack.top();
|
| dfs_stack.pop();
|
| @@ -480,7 +487,10 @@ syncer::SyncError BookmarkModelAssociator::BuildAssociations(
|
| }
|
|
|
| const BookmarkNode* child_node = NULL;
|
| - child_node = node_finder.FindBookmarkNode(sync_child_node);
|
| + child_node = node_finder.FindBookmarkNode(
|
| + GURL(sync_child_node.GetBookmarkSpecifics().url()),
|
| + sync_child_node.GetTitle(),
|
| + sync_child_node.GetIsFolder());
|
| if (child_node)
|
| Associate(child_node, sync_child_id);
|
| // All bookmarks are currently modified at association time (even if
|
| @@ -536,6 +546,98 @@ syncer::SyncError BookmarkModelAssociator::BuildAssociations(
|
| return syncer::SyncError();
|
| }
|
|
|
| +struct FolderInfo {
|
| + FolderInfo(const BookmarkNode* f, const BookmarkNode* p, int64 id)
|
| + : folder(f), parent(p), sync_id(id) {}
|
| + const BookmarkNode* folder;
|
| + const BookmarkNode* parent;
|
| + int64 sync_id;
|
| +};
|
| +typedef std::vector<FolderInfo> FolderInfoList;
|
| +
|
| +int64 BookmarkModelAssociator::ApplyDeletesFromSyncJournal(
|
| + syncer::BaseTransaction* trans) {
|
| + int64 num_bookmark_deleted = 0;
|
| +
|
| + syncer::BookmarkDeleteJournalList bk_delete_journals;
|
| + syncer::DeleteJournal::GetBookmarkDeleteJournals(trans, &bk_delete_journals);
|
| + if (bk_delete_journals.empty())
|
| + return 0;
|
| + size_t num_journals_unmatched = bk_delete_journals.size();
|
| +
|
| + // Check bookmark model from top to bottom.
|
| + std::stack<const BookmarkNode*> dfs_stack;
|
| + dfs_stack.push(bookmark_model_->bookmark_bar_node());
|
| + dfs_stack.push(bookmark_model_->other_node());
|
| + if (expect_mobile_bookmarks_folder_)
|
| + dfs_stack.push(bookmark_model_->mobile_node());
|
| +
|
| + // Remember folders that match delete journals in first pass but don't delete
|
| + // them in case there are bookmarks left under them. After non-folder
|
| + // bookmarks are removed in first pass, recheck the folders in reverse order
|
| + // to remove empty ones.
|
| + FolderInfoList folders_matched;
|
| + while (!dfs_stack.empty()) {
|
| + const BookmarkNode* parent = dfs_stack.top();
|
| + dfs_stack.pop();
|
| +
|
| + 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];
|
| + }
|
| + }
|
| + 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));
|
| + }
|
| + }
|
| +
|
| + // Ids of sync nodes not found in bookmark model, meaning the deletions are
|
| + // persisted and correponding delete journals can be dropped.
|
| + std::set<int64> journals_to_purge;
|
| +
|
| + // Remove empty folders from bottom to top.
|
| + for (FolderInfoList::reverse_iterator it = folders_matched.rbegin();
|
| + it != folders_matched.rend(); ++it) {
|
| + if (it->folder->child_count() == 0) {
|
| + bookmark_model_->Remove(it->parent, it->parent->GetIndexOf(it->folder));
|
| + ++num_bookmark_deleted;
|
| + } else {
|
| + // Keep non-empty folder and remove its journal so that it won't match
|
| + // again in the future.
|
| + journals_to_purge.insert(it->sync_id);
|
| + }
|
| + }
|
| +
|
| + // Purge unmatched journals.
|
| + for (size_t i = 0; i < num_journals_unmatched; ++i)
|
| + journals_to_purge.insert(bk_delete_journals[i].id);
|
| + syncer::DeleteJournal::PurgeDeleteJournals(trans, journals_to_purge);
|
| +
|
| + return num_bookmark_deleted;
|
| +}
|
| +
|
| void BookmarkModelAssociator::PostPersistAssociationsTask() {
|
| // No need to post a task if a task is already pending.
|
| if (weak_factory_.HasWeakPtrs())
|
|
|