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