blob: 9758ee9fc3dc2be2732783b804548a66b0fd19a7 [file] [log] [blame]
Mohamed Amir Yosefa89b53f62018-08-02 20:17:061// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_
6#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_
7
Raphael Kubo da Costacdf3e812019-12-16 11:39:118#include <memory>
Pauline Leitaod2928682019-09-21 15:26:469#include <string>
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0610#include <unordered_map>
11#include <vector>
12
13#include "base/macros.h"
14#include "components/sync/engine/non_blocking_sync_common.h"
15
16namespace bookmarks {
17class BookmarkModel;
18class BookmarkNode;
19} // namespace bookmarks
20
Mohamed Amir Yosef7266a9a2018-08-13 14:38:3321namespace favicon {
22class FaviconService;
23} // namespace favicon
24
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0625namespace sync_bookmarks {
26
27class SyncedBookmarkTracker;
28
29// Responsible for merging local and remote bookmark models when bookmark sync
30// is enabled for the first time by the user (i.e. no sync metadata exists
31// locally), so we need a best-effort merge based on similarity. It implements
32// similar logic to that in BookmarkModelAssociator::AssociateModels() to be
33// used by the BookmarkModelTypeProcessor().
34class BookmarkModelMerger {
35 public:
Mikel Astiz3681d6a2019-11-14 21:01:2336 // |bookmark_model|, |favicon_service| and |bookmark_tracker| must not be
37 // null and must outlive this object.
38 BookmarkModelMerger(syncer::UpdateResponseDataList updates,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0639 bookmarks::BookmarkModel* bookmark_model,
Mohamed Amir Yosef7266a9a2018-08-13 14:38:3340 favicon::FaviconService* favicon_service,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0641 SyncedBookmarkTracker* bookmark_tracker);
42
43 ~BookmarkModelMerger();
44
Mikel Astiz8f5dad92019-11-19 19:15:5245 // Merges the remote bookmark model represented as the updates received from
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0646 // the sync server and local bookmark model |bookmark_model|, and updates the
47 // model and |bookmark_tracker| (all of which are injected in the constructor)
48 // accordingly. On return, there will be a 1:1 mapping between bookmark nodes
49 // and metadata entities in the injected tracker.
50 void Merge();
51
52 private:
Mikel Astiz8f5dad92019-11-19 19:15:5253 // Internal representation of a remote tree, composed of nodes.
Raphael Kubo da Costacdf3e812019-12-16 11:39:1154 class RemoteTreeNode final {
55 private:
56 using UpdatesPerParentId =
Sergey Talantovff7224b2020-01-09 10:50:0757 std::unordered_map<std::string, syncer::UpdateResponseDataList>;
Raphael Kubo da Costacdf3e812019-12-16 11:39:1158
59 public:
60 // Constructs a tree given |update| as root and recursively all descendants
61 // by traversing |*updates_per_parent_id|. |update| and
62 // |updates_per_parent_id| must not be null. All updates
63 // |*updates_per_parent_id| must represent valid updates. Updates
64 // corresponding from descendant nodes are moved away from
65 // |*updates_per_parent_id|.
Sergey Talantovff7224b2020-01-09 10:50:0766 static RemoteTreeNode BuildTree(syncer::UpdateResponseData update,
67 UpdatesPerParentId* updates_per_parent_id);
Raphael Kubo da Costacdf3e812019-12-16 11:39:1168
69 ~RemoteTreeNode();
70
71 // Allow moves, useful during construction.
72 RemoteTreeNode(RemoteTreeNode&&);
73 RemoteTreeNode& operator=(RemoteTreeNode&&);
74
Sergey Talantovff7224b2020-01-09 10:50:0775 const syncer::EntityData& entity() const { return update_.entity; }
76 int64_t response_version() const { return update_.response_version; }
Raphael Kubo da Costacdf3e812019-12-16 11:39:1177
78 // Direct children nodes, sorted by ascending unique position. These are
79 // guaranteed to be valid updates (e.g. IsValidBookmarkSpecifics()).
80 const std::vector<RemoteTreeNode>& children() const { return children_; }
81
82 // Recursively emplaces all GUIDs (this node and descendants) into
83 // |*guid_to_remote_node_map|, which must not be null.
84 void EmplaceSelfAndDescendantsByGUID(
85 std::unordered_map<std::string, const RemoteTreeNode*>*
86 guid_to_remote_node_map) const;
87
88 private:
89 static bool UniquePositionLessThan(const RemoteTreeNode& lhs,
90 const RemoteTreeNode& rhs);
91
92 RemoteTreeNode();
93
Sergey Talantovff7224b2020-01-09 10:50:0794 syncer::UpdateResponseData update_;
Raphael Kubo da Costacdf3e812019-12-16 11:39:1195 std::vector<RemoteTreeNode> children_;
96 };
Mikel Astiz8f5dad92019-11-19 19:15:5297
98 // A forest composed of multiple trees where the root of each tree represents
99 // a permanent node, keyed by server-defined unique tag of the root.
100 using RemoteForest = std::unordered_map<std::string, RemoteTreeNode>;
101
Mikel Astizafc87702019-11-25 10:53:45102 // Represents a pair of bookmarks, one local and one remote, that have been
103 // matched by GUID. They are guaranteed to have the same type and URL (if
104 // applicable).
105 struct GuidMatch {
106 const bookmarks::BookmarkNode* local_node;
107 const RemoteTreeNode* remote_node;
108 };
109
Mikel Astiz8f5dad92019-11-19 19:15:52110 // Constructs the remote bookmark tree to be merged. Each entry in the
111 // returned map is a permanent node, identified (keyed) by the server-defined
112 // tag. All invalid updates are filtered out, including invalid bookmark
113 // specifics as well as tombstones, in the unlikely event that the server
114 // sends tombstones as part of the initial download.
115 static RemoteForest BuildRemoteForest(syncer::UpdateResponseDataList updates);
116
Mikel Astizafc87702019-11-25 10:53:45117 // Computes bookmark pairs that should be matched by GUID. Local bookmark
118 // GUIDs may be regenerated for the case where they collide with a remote GUID
119 // that is not compatible (e.g. folder vs non-folder).
120 static std::unordered_map<std::string, GuidMatch>
121 FindGuidMatchesOrReassignLocal(const RemoteForest& remote_forest,
122 bookmarks::BookmarkModel* bookmark_model);
Mikel Astiz8f5dad92019-11-19 19:15:52123
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06124 // Merges a local and a remote subtrees. The input nodes are two equivalent
125 // local and remote nodes. This method tries to recursively match their
126 // children. It updates the |bookmark_tracker_| accordingly.
127 void MergeSubtree(const bookmarks::BookmarkNode* local_node,
Mikel Astiz8f5dad92019-11-19 19:15:52128 const RemoteTreeNode& remote_node);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06129
Mikel Astiz8f5dad92019-11-19 19:15:52130 // Updates |local_node| to hold same GUID and semantics as its |remote_node|
Pauline Leitaod2928682019-09-21 15:26:46131 // match. The input nodes are two equivalent local and remote bookmarks that
132 // are about to be merged. The output node is the potentially replaced
133 // |local_node|. |local_node| must not be a BookmarkPermanentNode.
134 const bookmarks::BookmarkNode* UpdateBookmarkNodeFromSpecificsIncludingGUID(
135 const bookmarks::BookmarkNode* local_node,
Mikel Astiz8f5dad92019-11-19 19:15:52136 const RemoteTreeNode& remote_node);
Pauline Leitaod2928682019-09-21 15:26:46137
Mikel Astiz8f5dad92019-11-19 19:15:52138 // Creates a local bookmark node for a |remote_node|. The local node is
Pauline Leitaod2928682019-09-21 15:26:46139 // created under |local_parent| at position |index|. If the remote node has
140 // children, this method recursively creates them as well. It updates the
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06141 // |bookmark_tracker_| accordingly.
Mikel Astiz8f5dad92019-11-19 19:15:52142 void ProcessRemoteCreation(const RemoteTreeNode& remote_node,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06143 const bookmarks::BookmarkNode* local_parent,
Peter Kasting8cf4c23e2019-06-06 00:38:30144 size_t index);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06145
146 // Creates a server counter-part for the local node at position |index|
147 // under |parent|. If the local node has children, corresponding server nodes
148 // are created recursively. It updates the |bookmark_tracker_| accordingly and
149 // new nodes are marked to be committed.
Peter Kasting8cf4c23e2019-06-06 00:38:30150 void ProcessLocalCreation(const bookmarks::BookmarkNode* parent,
151 size_t index);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06152
Mikel Astiz8f5dad92019-11-19 19:15:52153 // Looks for a local node under |local_parent| that matches |remote_node|,
154 // starting at index |local_child_start_index|. First attempts to find a match
155 // by GUID and otherwise attempts to find one by semantics. If no match is
156 // found, a nullptr is returned.
Pauline Leitaod2928682019-09-21 15:26:46157 const bookmarks::BookmarkNode* FindMatchingLocalNode(
Mikel Astiz8f5dad92019-11-19 19:15:52158 const RemoteTreeNode& remote_node,
Pauline Leitaod2928682019-09-21 15:26:46159 const bookmarks::BookmarkNode* local_parent,
160 size_t local_child_start_index) const;
161
162 // If |local_node| has a remote counterpart of the same GUID, returns the
Mikel Astiz8f5dad92019-11-19 19:15:52163 // corresponding remote node, otherwise returns a nullptr. |local_node| must
Pauline Leitaod2928682019-09-21 15:26:46164 // not be null.
Mikel Astiz8f5dad92019-11-19 19:15:52165 const RemoteTreeNode* FindMatchingRemoteNodeByGUID(
Pauline Leitaod2928682019-09-21 15:26:46166 const bookmarks::BookmarkNode* local_node) const;
167
Mikel Astiz8f5dad92019-11-19 19:15:52168 // If |remote_node| has a local counterpart of the same GUID, returns the
169 // corresponding local node, otherwise returns a nullptr.
Pauline Leitaod2928682019-09-21 15:26:46170 const bookmarks::BookmarkNode* FindMatchingLocalNodeByGUID(
Mikel Astiz8f5dad92019-11-19 19:15:52171 const RemoteTreeNode& remote_node) const;
Pauline Leitaod2928682019-09-21 15:26:46172
173 // Tries to find a child local node under |local_parent| that matches
174 // |remote_node| semantically and returns the index of that node, as long as
175 // this local child cannot be matched by GUID to a different node. Matching is
176 // decided using NodeSemanticsMatch(). It searches in the children list
177 // starting from position |search_starting_child_index|. In case of no match
178 // is found, it returns |kInvalidIndex|.
179 size_t FindMatchingChildBySemanticsStartingAt(
Mikel Astiz8f5dad92019-11-19 19:15:52180 const RemoteTreeNode& remote_node,
Pauline Leitaod2928682019-09-21 15:26:46181 const bookmarks::BookmarkNode* local_parent,
182 size_t starting_child_index) const;
183
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06184 bookmarks::BookmarkModel* const bookmark_model_;
Mohamed Amir Yosef7266a9a2018-08-13 14:38:33185 favicon::FaviconService* const favicon_service_;
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06186 SyncedBookmarkTracker* const bookmark_tracker_;
Mikel Astiz8f5dad92019-11-19 19:15:52187 // Preprocessed remote nodes in the form a forest where each tree's root is a
188 // permanent node. Computed upon construction via BuildRemoteForest().
189 const RemoteForest remote_forest_;
Mikel Astizafc87702019-11-25 10:53:45190 std::unordered_map<std::string, GuidMatch> guid_to_match_map_;
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06191
192 DISALLOW_COPY_AND_ASSIGN(BookmarkModelMerger);
193};
194
195} // namespace sync_bookmarks
196
197#endif // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_