OLD | NEW |
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/sync/glue/bookmark_model_associator.h" | 5 #include "chrome/browser/sync/glue/bookmark_model_associator.h" |
6 | 6 |
7 #include <stack> | 7 #include <stack> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
69 const int kTitleLimitBytes = 255; | 69 const int kTitleLimitBytes = 255; |
70 | 70 |
71 // Provides the following abstraction: given a parent bookmark node, find best | 71 // Provides the following abstraction: given a parent bookmark node, find best |
72 // matching child node for many sync nodes. | 72 // matching child node for many sync nodes. |
73 class BookmarkNodeFinder { | 73 class BookmarkNodeFinder { |
74 public: | 74 public: |
75 // Creates an instance with the given parent bookmark node. | 75 // Creates an instance with the given parent bookmark node. |
76 explicit BookmarkNodeFinder(const BookmarkNode* parent_node); | 76 explicit BookmarkNodeFinder(const BookmarkNode* parent_node); |
77 | 77 |
78 // Finds the bookmark node that matches the given url, title and folder | 78 // Finds the bookmark node that matches the given url, title and folder |
79 // attribute. Returns the matching node if one exists; NULL otherwise. If a | 79 // attribute. Returns the matching node if one exists; NULL otherwise. |
80 // matching node is found, it's removed for further matches. | 80 // If there are multiple matches then a node with ID matching |preferred_id| |
| 81 // is returned; otherwise the first matching node is returned. |
| 82 // If a matching node is found, it's removed for further matches. |
81 const BookmarkNode* FindBookmarkNode(const GURL& url, | 83 const BookmarkNode* FindBookmarkNode(const GURL& url, |
82 const std::string& title, | 84 const std::string& title, |
83 bool is_folder); | 85 bool is_folder, |
| 86 int64 preferred_id); |
| 87 |
| 88 // Returns true if |bookmark_node| matches the specified |url|, |
| 89 // |title|, and |is_folder| flags. |
| 90 static bool NodeMatches(const BookmarkNode* bookmark_node, |
| 91 const GURL& url, |
| 92 const std::string& title, |
| 93 bool is_folder); |
84 | 94 |
85 private: | 95 private: |
86 // Maps bookmark node titles to instances, duplicates allowed. | 96 // Maps bookmark node titles to instances, duplicates allowed. |
87 // Titles are converted to the sync internal format before | 97 // Titles are converted to the sync internal format before |
88 // being used as keys for the map. | 98 // being used as keys for the map. |
89 typedef base::hash_multimap<std::string, | 99 typedef base::hash_multimap<std::string, |
90 const BookmarkNode*> BookmarkNodeMap; | 100 const BookmarkNode*> BookmarkNodeMap; |
91 typedef std::pair<BookmarkNodeMap::iterator, | 101 typedef std::pair<BookmarkNodeMap::iterator, |
92 BookmarkNodeMap::iterator> BookmarkNodeRange; | 102 BookmarkNodeMap::iterator> BookmarkNodeRange; |
93 | 103 |
94 // Converts and truncates bookmark titles in the form sync does internally | 104 // Converts and truncates bookmark titles in the form sync does internally |
95 // to avoid mismatches due to sync munging titles. | 105 // to avoid mismatches due to sync munging titles. |
96 void ConvertTitleToSyncInternalFormat( | 106 static void ConvertTitleToSyncInternalFormat(const std::string& input, |
97 const std::string& input, std::string* output); | 107 std::string* output); |
98 | 108 |
99 const BookmarkNode* parent_node_; | 109 const BookmarkNode* parent_node_; |
100 BookmarkNodeMap child_nodes_; | 110 BookmarkNodeMap child_nodes_; |
101 | 111 |
102 DISALLOW_COPY_AND_ASSIGN(BookmarkNodeFinder); | 112 DISALLOW_COPY_AND_ASSIGN(BookmarkNodeFinder); |
103 }; | 113 }; |
104 | 114 |
105 class ScopedAssociationUpdater { | 115 class ScopedAssociationUpdater { |
106 public: | 116 public: |
107 explicit ScopedAssociationUpdater(BookmarkModel* model) { | 117 explicit ScopedAssociationUpdater(BookmarkModel* model) { |
(...skipping 17 matching lines...) Expand all Loading... |
125 const BookmarkNode* child_node = parent_node_->GetChild(i); | 135 const BookmarkNode* child_node = parent_node_->GetChild(i); |
126 | 136 |
127 std::string title = base::UTF16ToUTF8(child_node->GetTitle()); | 137 std::string title = base::UTF16ToUTF8(child_node->GetTitle()); |
128 ConvertTitleToSyncInternalFormat(title, &title); | 138 ConvertTitleToSyncInternalFormat(title, &title); |
129 | 139 |
130 child_nodes_.insert(std::make_pair(title, child_node)); | 140 child_nodes_.insert(std::make_pair(title, child_node)); |
131 } | 141 } |
132 } | 142 } |
133 | 143 |
134 const BookmarkNode* BookmarkNodeFinder::FindBookmarkNode( | 144 const BookmarkNode* BookmarkNodeFinder::FindBookmarkNode( |
135 const GURL& url, const std::string& title, bool is_folder) { | 145 const GURL& url, |
| 146 const std::string& title, |
| 147 bool is_folder, |
| 148 int64 preferred_id) { |
| 149 const BookmarkNode* match = nullptr; |
| 150 |
136 // First lookup a range of bookmarks with the same title. | 151 // First lookup a range of bookmarks with the same title. |
137 std::string adjusted_title; | 152 std::string adjusted_title; |
138 ConvertTitleToSyncInternalFormat(title, &adjusted_title); | 153 ConvertTitleToSyncInternalFormat(title, &adjusted_title); |
139 BookmarkNodeRange range = child_nodes_.equal_range(adjusted_title); | 154 BookmarkNodeRange range = child_nodes_.equal_range(adjusted_title); |
| 155 BookmarkNodeMap::iterator match_iter = range.second; |
140 for (BookmarkNodeMap::iterator iter = range.first; | 156 for (BookmarkNodeMap::iterator iter = range.first; |
141 iter != range.second; | 157 iter != range.second; |
142 ++iter) { | 158 ++iter) { |
143 | |
144 // Then within the range match the node by the folder bit | 159 // Then within the range match the node by the folder bit |
145 // and the url. | 160 // and the url. |
146 const BookmarkNode* node = iter->second; | 161 const BookmarkNode* node = iter->second; |
147 if (is_folder == node->is_folder() && url == node->url()) { | 162 if (is_folder == node->is_folder() && url == node->url()) { |
148 // Remove the matched node so we don't match with it again. | 163 if (node->id() == preferred_id || preferred_id == 0) { |
149 child_nodes_.erase(iter); | 164 // Preferred match - use this node. |
150 return node; | 165 match = node; |
| 166 match_iter = iter; |
| 167 break; |
| 168 } else if (match == nullptr) { |
| 169 // First match - continue iterating. |
| 170 match = node; |
| 171 match_iter = iter; |
| 172 } |
151 } | 173 } |
152 } | 174 } |
153 | 175 |
154 return NULL; | 176 if (match_iter != range.second) { |
| 177 // Remove the matched node so we don't match with it again. |
| 178 child_nodes_.erase(match_iter); |
| 179 } |
| 180 |
| 181 return match; |
155 } | 182 } |
156 | 183 |
| 184 /* static */ |
| 185 bool BookmarkNodeFinder::NodeMatches(const BookmarkNode* bookmark_node, |
| 186 const GURL& url, |
| 187 const std::string& title, |
| 188 bool is_folder) { |
| 189 if (url != bookmark_node->url() || is_folder != bookmark_node->is_folder()) { |
| 190 return false; |
| 191 } |
| 192 |
| 193 // The title passed to this method comes from a sync directory entry. |
| 194 // The following two lines are needed to make the native bookmark title |
| 195 // comparable. The same conversion is used in BookmarkNodeFinder constructor. |
| 196 std::string bookmark_title = base::UTF16ToUTF8(bookmark_node->GetTitle()); |
| 197 ConvertTitleToSyncInternalFormat(bookmark_title, &bookmark_title); |
| 198 return title == bookmark_title; |
| 199 } |
| 200 |
| 201 /* static */ |
157 void BookmarkNodeFinder::ConvertTitleToSyncInternalFormat( | 202 void BookmarkNodeFinder::ConvertTitleToSyncInternalFormat( |
158 const std::string& input, std::string* output) { | 203 const std::string& input, std::string* output) { |
159 syncer::SyncAPINameToServerName(input, output); | 204 syncer::SyncAPINameToServerName(input, output); |
160 base::TruncateUTF8ToByteSize(*output, kTitleLimitBytes, output); | 205 base::TruncateUTF8ToByteSize(*output, kTitleLimitBytes, output); |
161 } | 206 } |
162 | 207 |
163 // Helper class to build an index of bookmark nodes by their IDs. | 208 // Helper class to build an index of bookmark nodes by their IDs. |
164 class BookmarkNodeIdIndex { | 209 class BookmarkNodeIdIndex { |
165 public: | 210 public: |
166 BookmarkNodeIdIndex() { } | 211 BookmarkNodeIdIndex() { } |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 } | 384 } |
340 | 385 |
341 // Sync model has user created nodes if any of the permanent nodes has | 386 // Sync model has user created nodes if any of the permanent nodes has |
342 // children. | 387 // children. |
343 *has_nodes = bookmark_bar_node.HasChildren() || | 388 *has_nodes = bookmark_bar_node.HasChildren() || |
344 other_bookmarks_node.HasChildren() || | 389 other_bookmarks_node.HasChildren() || |
345 (has_mobile_folder && mobile_bookmarks_node.HasChildren()); | 390 (has_mobile_folder && mobile_bookmarks_node.HasChildren()); |
346 return true; | 391 return true; |
347 } | 392 } |
348 | 393 |
349 bool BookmarkModelAssociator::NodesMatch( | |
350 const BookmarkNode* bookmark, | |
351 const syncer::BaseNode* sync_node) const { | |
352 std::string truncated_title = base::UTF16ToUTF8(bookmark->GetTitle()); | |
353 base::TruncateUTF8ToByteSize(truncated_title, | |
354 kTitleLimitBytes, | |
355 &truncated_title); | |
356 if (truncated_title != sync_node->GetTitle()) | |
357 return false; | |
358 if (bookmark->is_folder() != sync_node->GetIsFolder()) | |
359 return false; | |
360 if (bookmark->is_url()) { | |
361 if (bookmark->url() != GURL(sync_node->GetBookmarkSpecifics().url())) | |
362 return false; | |
363 } | |
364 // Don't compare favicons here, because they are not really | |
365 // user-updated and we don't have versioning information -- a site changing | |
366 // its favicon shouldn't result in a bookmark mismatch. | |
367 return true; | |
368 } | |
369 | |
370 bool BookmarkModelAssociator::AssociateTaggedPermanentNode( | 394 bool BookmarkModelAssociator::AssociateTaggedPermanentNode( |
371 const BookmarkNode* permanent_node, const std::string&tag) { | 395 const BookmarkNode* permanent_node, const std::string&tag) { |
372 // Do nothing if |permanent_node| is already initialized and associated. | 396 // Do nothing if |permanent_node| is already initialized and associated. |
373 int64 sync_id = GetSyncIdFromChromeId(permanent_node->id()); | 397 int64 sync_id = GetSyncIdFromChromeId(permanent_node->id()); |
374 if (sync_id != syncer::kInvalidId) | 398 if (sync_id != syncer::kInvalidId) |
375 return true; | 399 return true; |
376 if (!GetSyncIdForTaggedNode(tag, &sync_id)) | 400 if (!GetSyncIdForTaggedNode(tag, &sync_id)) |
377 return false; | 401 return false; |
378 | 402 |
379 Associate(permanent_node, sync_id); | 403 Associate(permanent_node, sync_id); |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
417 // Match up the roots and recursively do the following: | 441 // Match up the roots and recursively do the following: |
418 // * For each sync node for the current sync parent node, find the best | 442 // * For each sync node for the current sync parent node, find the best |
419 // matching bookmark node under the corresponding bookmark parent node. | 443 // matching bookmark node under the corresponding bookmark parent node. |
420 // If no matching node is found, create a new bookmark node in the same | 444 // If no matching node is found, create a new bookmark node in the same |
421 // position as the corresponding sync node. | 445 // position as the corresponding sync node. |
422 // If a matching node is found, update the properties of it from the | 446 // If a matching node is found, update the properties of it from the |
423 // corresponding sync node. | 447 // corresponding sync node. |
424 // * When all children sync nodes are done, add the extra children bookmark | 448 // * When all children sync nodes are done, add the extra children bookmark |
425 // nodes to the sync parent node. | 449 // nodes to the sync parent node. |
426 // | 450 // |
427 // This algorithm will do a good job of merging when folder names are a good | 451 // The best match algorithm uses folder title or bookmark title/url to |
428 // indicator of the two folders being the same. It will handle reordering and | 452 // perform the primary match. If there are multiple match candidates it |
429 // new node addition very well (without creating duplicates). | 453 // selects the preferred one based on sync node external ID match to the |
430 // This algorithm will not do well if the folder name has changes but the | 454 // bookmark folder ID. |
431 // children under them are all the same. | |
432 | 455 |
433 DCHECK(bookmark_model_->loaded()); | 456 DCHECK(bookmark_model_->loaded()); |
434 | 457 |
435 // To prime our association, we associate the top-level nodes, Bookmark Bar | 458 // To prime our association, we associate the top-level nodes, Bookmark Bar |
436 // and Other Bookmarks. | 459 // and Other Bookmarks. |
437 if (!AssociateTaggedPermanentNode(bookmark_model_->bookmark_bar_node(), | 460 if (!AssociateTaggedPermanentNode(bookmark_model_->bookmark_bar_node(), |
438 kBookmarkBarTag)) { | 461 kBookmarkBarTag)) { |
439 return unrecoverable_error_handler_->CreateAndUploadError( | 462 return unrecoverable_error_handler_->CreateAndUploadError( |
440 FROM_HERE, | 463 FROM_HERE, |
441 "Bookmark bar node not found", | 464 "Bookmark bar node not found", |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
485 syncer::WriteTransaction trans(FROM_HERE, user_share_); | 508 syncer::WriteTransaction trans(FROM_HERE, user_share_); |
486 syncer::ReadNode bm_root(&trans); | 509 syncer::ReadNode bm_root(&trans); |
487 if (bm_root.InitTypeRoot(syncer::BOOKMARKS) == syncer::BaseNode::INIT_OK) { | 510 if (bm_root.InitTypeRoot(syncer::BOOKMARKS) == syncer::BaseNode::INIT_OK) { |
488 syncer_merge_result->set_num_items_before_association( | 511 syncer_merge_result->set_num_items_before_association( |
489 bm_root.GetTotalNodeCount()); | 512 bm_root.GetTotalNodeCount()); |
490 } | 513 } |
491 local_merge_result->set_num_items_before_association( | 514 local_merge_result->set_num_items_before_association( |
492 bookmark_model_->root_node()->GetTotalNodeCount()); | 515 bookmark_model_->root_node()->GetTotalNodeCount()); |
493 | 516 |
494 // Remove obsolete bookmarks according to sync delete journal. | 517 // Remove obsolete bookmarks according to sync delete journal. |
| 518 // TODO (stanisc): crbug.com/456876: rewrite this to avoid a separate |
| 519 // traversal and instead perform deletes at the end of the loop below where |
| 520 // the unmatched bookmark nodes are created as sync nodes. |
495 local_merge_result->set_num_items_deleted( | 521 local_merge_result->set_num_items_deleted( |
496 ApplyDeletesFromSyncJournal(&trans)); | 522 ApplyDeletesFromSyncJournal(&trans)); |
497 | 523 |
498 while (!dfs_stack.empty()) { | 524 while (!dfs_stack.empty()) { |
499 int64 sync_parent_id = dfs_stack.top(); | 525 int64 sync_parent_id = dfs_stack.top(); |
500 dfs_stack.pop(); | 526 dfs_stack.pop(); |
501 | 527 |
502 syncer::ReadNode sync_parent(&trans); | 528 syncer::ReadNode sync_parent(&trans); |
503 if (sync_parent.InitByIdLookup(sync_parent_id) != | 529 if (sync_parent.InitByIdLookup(sync_parent_id) != |
504 syncer::BaseNode::INIT_OK) { | 530 syncer::BaseNode::INIT_OK) { |
(...skipping 22 matching lines...) Expand all Loading... |
527 int64 sync_child_id = *it; | 553 int64 sync_child_id = *it; |
528 syncer::ReadNode sync_child_node(&trans); | 554 syncer::ReadNode sync_child_node(&trans); |
529 if (sync_child_node.InitByIdLookup(sync_child_id) != | 555 if (sync_child_node.InitByIdLookup(sync_child_id) != |
530 syncer::BaseNode::INIT_OK) { | 556 syncer::BaseNode::INIT_OK) { |
531 return unrecoverable_error_handler_->CreateAndUploadError( | 557 return unrecoverable_error_handler_->CreateAndUploadError( |
532 FROM_HERE, | 558 FROM_HERE, |
533 "Failed to lookup node.", | 559 "Failed to lookup node.", |
534 model_type()); | 560 model_type()); |
535 } | 561 } |
536 | 562 |
537 const BookmarkNode* child_node = NULL; | 563 const BookmarkNode* child_node = node_finder.FindBookmarkNode( |
538 child_node = node_finder.FindBookmarkNode( | |
539 GURL(sync_child_node.GetBookmarkSpecifics().url()), | 564 GURL(sync_child_node.GetBookmarkSpecifics().url()), |
540 sync_child_node.GetTitle(), | 565 sync_child_node.GetTitle(), sync_child_node.GetIsFolder(), |
541 sync_child_node.GetIsFolder()); | 566 sync_child_node.GetExternalId()); |
542 if (child_node) { | 567 if (child_node) { |
543 Associate(child_node, sync_child_id); | 568 Associate(child_node, sync_child_id); |
544 | 569 |
545 // All bookmarks are currently modified at association time, even if | 570 // All bookmarks are currently modified at association time, even if |
546 // nothing has changed. | 571 // nothing has changed. |
547 // TODO(sync): Only modify the bookmark model if necessary. | 572 // TODO(sync): Only modify the bookmark model if necessary. |
548 BookmarkChangeProcessor::UpdateBookmarkWithSyncData( | 573 BookmarkChangeProcessor::UpdateBookmarkWithSyncData( |
549 sync_child_node, bookmark_model_, child_node, profile_); | 574 sync_child_node, bookmark_model_, child_node, profile_); |
550 bookmark_model_->Move(child_node, parent_node, index); | 575 bookmark_model_->Move(child_node, parent_node, index); |
551 local_merge_result->set_num_items_modified( | 576 local_merge_result->set_num_items_modified( |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
609 int64 BookmarkModelAssociator::ApplyDeletesFromSyncJournal( | 634 int64 BookmarkModelAssociator::ApplyDeletesFromSyncJournal( |
610 syncer::BaseTransaction* trans) { | 635 syncer::BaseTransaction* trans) { |
611 int64 num_bookmark_deleted = 0; | 636 int64 num_bookmark_deleted = 0; |
612 | 637 |
613 syncer::BookmarkDeleteJournalList bk_delete_journals; | 638 syncer::BookmarkDeleteJournalList bk_delete_journals; |
614 syncer::DeleteJournal::GetBookmarkDeleteJournals(trans, &bk_delete_journals); | 639 syncer::DeleteJournal::GetBookmarkDeleteJournals(trans, &bk_delete_journals); |
615 if (bk_delete_journals.empty()) | 640 if (bk_delete_journals.empty()) |
616 return 0; | 641 return 0; |
617 size_t num_journals_unmatched = bk_delete_journals.size(); | 642 size_t num_journals_unmatched = bk_delete_journals.size(); |
618 | 643 |
| 644 // Make a set of all external IDs in the delete journal, |
| 645 // ignore entries with unset external IDs. |
| 646 std::set<int64> journaled_external_ids; |
| 647 for (size_t i = 0; i < num_journals_unmatched; i++) { |
| 648 if (bk_delete_journals[i].external_id != 0) |
| 649 journaled_external_ids.insert(bk_delete_journals[i].external_id); |
| 650 } |
| 651 |
619 // Check bookmark model from top to bottom. | 652 // Check bookmark model from top to bottom. |
620 std::stack<const BookmarkNode*> dfs_stack; | 653 std::stack<const BookmarkNode*> dfs_stack; |
621 dfs_stack.push(bookmark_model_->bookmark_bar_node()); | 654 dfs_stack.push(bookmark_model_->bookmark_bar_node()); |
622 dfs_stack.push(bookmark_model_->other_node()); | 655 dfs_stack.push(bookmark_model_->other_node()); |
623 if (expect_mobile_bookmarks_folder_) | 656 if (expect_mobile_bookmarks_folder_) |
624 dfs_stack.push(bookmark_model_->mobile_node()); | 657 dfs_stack.push(bookmark_model_->mobile_node()); |
625 // Note: the root node may have additional extra nodes. Currently none of | 658 // Note: the root node may have additional extra nodes. Currently none of |
626 // them are meant to sync. | 659 // them are meant to sync. |
627 | 660 |
628 // Remember folders that match delete journals in first pass but don't delete | 661 // Remember folders that match delete journals in first pass but don't delete |
629 // them in case there are bookmarks left under them. After non-folder | 662 // them in case there are bookmarks left under them. After non-folder |
630 // bookmarks are removed in first pass, recheck the folders in reverse order | 663 // bookmarks are removed in first pass, recheck the folders in reverse order |
631 // to remove empty ones. | 664 // to remove empty ones. |
632 FolderInfoList folders_matched; | 665 FolderInfoList folders_matched; |
633 while (!dfs_stack.empty()) { | 666 while (!dfs_stack.empty() && num_journals_unmatched > 0) { |
634 const BookmarkNode* parent = dfs_stack.top(); | 667 const BookmarkNode* parent = dfs_stack.top(); |
635 dfs_stack.pop(); | 668 dfs_stack.pop(); |
| 669 DCHECK(parent->is_folder()); |
636 | 670 |
637 BookmarkNodeFinder finder(parent); | 671 // Enumerate folder children in reverse order to make it easier to remove |
638 // Iterate through journals from back to front. Remove matched journal by | 672 // bookmarks matching entries in the delete journal. |
639 // moving an unmatched journal at the tail to its position so that we can | 673 for (int child_index = parent->child_count() - 1; |
640 // read unmatched journals off the head in next loop. | 674 child_index >= 0 && num_journals_unmatched > 0; --child_index) { |
641 for (int i = num_journals_unmatched - 1; i >= 0; --i) { | 675 const BookmarkNode* child = parent->GetChild(child_index); |
642 const BookmarkNode* child = finder.FindBookmarkNode( | 676 if (child->is_folder()) |
643 GURL(bk_delete_journals[i].specifics.bookmark().url()), | 677 dfs_stack.push(child); |
644 bk_delete_journals[i].specifics.bookmark().title(), | 678 |
645 bk_delete_journals[i].is_folder); | 679 if (journaled_external_ids.find(child->id()) == |
646 if (child) { | 680 journaled_external_ids.end()) { |
647 if (child->is_folder()) { | 681 // Skip bookmark node which id is not in the set of external IDs. |
648 // Remember matched folder without removing and delete only empty | 682 continue; |
649 // ones later. | 683 } |
650 folders_matched.push_back(FolderInfo(child, parent, | 684 |
651 bk_delete_journals[i].id)); | 685 // Iterate through the journal entries from back to front. Remove matched |
652 } else { | 686 // journal by moving an unmatched entry at the tail to the matched |
653 bookmark_model_->Remove(parent, parent->GetIndexOf(child)); | 687 // position so that we can read unmatched entries off the head in next |
654 ++num_bookmark_deleted; | 688 // loop. |
| 689 for (int journal_index = num_journals_unmatched - 1; journal_index >= 0; |
| 690 --journal_index) { |
| 691 const syncer::BookmarkDeleteJournal& delete_entry = |
| 692 bk_delete_journals[journal_index]; |
| 693 if (child->id() == delete_entry.external_id && |
| 694 BookmarkNodeFinder::NodeMatches( |
| 695 child, GURL(delete_entry.specifics.bookmark().url()), |
| 696 delete_entry.specifics.bookmark().title(), |
| 697 delete_entry.is_folder)) { |
| 698 if (child->is_folder()) { |
| 699 // Remember matched folder without removing and delete only empty |
| 700 // ones later. |
| 701 folders_matched.push_back( |
| 702 FolderInfo(child, parent, delete_entry.id)); |
| 703 } else { |
| 704 bookmark_model_->Remove(parent, child_index); |
| 705 ++num_bookmark_deleted; |
| 706 } |
| 707 // Move unmatched journal here and decrement counter. |
| 708 bk_delete_journals[journal_index] = |
| 709 bk_delete_journals[--num_journals_unmatched]; |
| 710 break; |
655 } | 711 } |
656 // Move unmatched journal here and decrement counter. | |
657 bk_delete_journals[i] = bk_delete_journals[--num_journals_unmatched]; | |
658 } | 712 } |
659 } | 713 } |
660 if (num_journals_unmatched == 0) | |
661 break; | |
662 | |
663 for (int i = 0; i < parent->child_count(); ++i) { | |
664 if (parent->GetChild(i)->is_folder()) | |
665 dfs_stack.push(parent->GetChild(i)); | |
666 } | |
667 } | 714 } |
668 | 715 |
669 // Ids of sync nodes not found in bookmark model, meaning the deletions are | 716 // Ids of sync nodes not found in bookmark model, meaning the deletions are |
670 // persisted and correponding delete journals can be dropped. | 717 // persisted and correponding delete journals can be dropped. |
671 std::set<int64> journals_to_purge; | 718 std::set<int64> journals_to_purge; |
672 | 719 |
673 // Remove empty folders from bottom to top. | 720 // Remove empty folders from bottom to top. |
674 for (FolderInfoList::reverse_iterator it = folders_matched.rbegin(); | 721 for (FolderInfoList::reverse_iterator it = folders_matched.rbegin(); |
675 it != folders_matched.rend(); ++it) { | 722 it != folders_matched.rend(); ++it) { |
676 if (it->folder->child_count() == 0) { | 723 if (it->folder->child_count() == 0) { |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
787 syncer::SyncError::PERSISTENCE_ERROR, | 834 syncer::SyncError::PERSISTENCE_ERROR, |
788 message, | 835 message, |
789 syncer::BOOKMARKS); | 836 syncer::BOOKMARKS); |
790 } | 837 } |
791 } | 838 } |
792 } | 839 } |
793 return syncer::SyncError(); | 840 return syncer::SyncError(); |
794 } | 841 } |
795 | 842 |
796 } // namespace browser_sync | 843 } // namespace browser_sync |
OLD | NEW |