blob: 0020096df6f0af036a32b74b040b55862c5e0e8f [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
Rushan Suleymanovc414e152020-05-19 21:26:548#include <list>
Raphael Kubo da Costacdf3e812019-12-16 11:39:119#include <memory>
Pauline Leitaod2928682019-09-21 15:26:4610#include <string>
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0611#include <unordered_map>
12#include <vector>
13
Daniel Hosseinian6de24fa2020-11-17 19:19:5314#include "base/guid.h"
Keishi Hattori0e45c022021-11-27 09:25:5215#include "base/memory/raw_ptr.h"
Rushan Suleymanov84c5295c2020-04-10 08:01:4716#include "components/sync/base/unique_position.h"
Victor Hugo Vianna Silva722d50f2020-10-13 16:58:1217#include "components/sync/engine/commit_and_get_updates_types.h"
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0618
19namespace bookmarks {
20class BookmarkModel;
21class BookmarkNode;
22} // namespace bookmarks
23
Mohamed Amir Yosef7266a9a2018-08-13 14:38:3324namespace favicon {
25class FaviconService;
26} // namespace favicon
27
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0628namespace sync_bookmarks {
29
30class SyncedBookmarkTracker;
31
32// Responsible for merging local and remote bookmark models when bookmark sync
33// is enabled for the first time by the user (i.e. no sync metadata exists
Zhuoyu Qian7d6ba79a2022-04-13 15:01:3134// locally), so we need a best-effort merge based on similarity. It is used by
35// the BookmarkModelTypeProcessor().
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0636class BookmarkModelMerger {
37 public:
Mikel Astiz3681d6a2019-11-14 21:01:2338 // |bookmark_model|, |favicon_service| and |bookmark_tracker| must not be
39 // null and must outlive this object.
40 BookmarkModelMerger(syncer::UpdateResponseDataList updates,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0641 bookmarks::BookmarkModel* bookmark_model,
Mohamed Amir Yosef7266a9a2018-08-13 14:38:3342 favicon::FaviconService* favicon_service,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0643 SyncedBookmarkTracker* bookmark_tracker);
44
Peter Boström09c01822021-09-20 22:43:2745 BookmarkModelMerger(const BookmarkModelMerger&) = delete;
46 BookmarkModelMerger& operator=(const BookmarkModelMerger&) = delete;
47
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0648 ~BookmarkModelMerger();
49
Mikel Astiz8f5dad92019-11-19 19:15:5250 // Merges the remote bookmark model represented as the updates received from
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0651 // the sync server and local bookmark model |bookmark_model|, and updates the
52 // model and |bookmark_tracker| (all of which are injected in the constructor)
53 // accordingly. On return, there will be a 1:1 mapping between bookmark nodes
54 // and metadata entities in the injected tracker.
55 void Merge();
56
57 private:
Mikel Astiz8f5dad92019-11-19 19:15:5258 // Internal representation of a remote tree, composed of nodes.
Raphael Kubo da Costacdf3e812019-12-16 11:39:1159 class RemoteTreeNode final {
60 private:
Mikel Astiz68c787e2021-12-19 08:46:4961 using UpdatesPerParentGUID =
62 std::unordered_map<base::GUID,
63 std::list<syncer::UpdateResponseData>,
64 base::GUIDHash>;
Raphael Kubo da Costacdf3e812019-12-16 11:39:1165
66 public:
67 // Constructs a tree given |update| as root and recursively all descendants
Mikel Astiz68c787e2021-12-19 08:46:4968 // by traversing |*updates_per_parent_guid|. |update| and
69 // |updates_per_parent_guid| must not be null. All updates
70 // |*updates_per_parent_guid| must represent valid updates. Updates
Raphael Kubo da Costacdf3e812019-12-16 11:39:1171 // corresponding from descendant nodes are moved away from
Mikel Astiz68c787e2021-12-19 08:46:4972 // |*updates_per_parent_guid|. |max_depth| is the max tree depth to sync
Chris Davis96a12c72020-06-26 17:53:3673 // after which content is silently ignored.
Mikel Astiz68c787e2021-12-19 08:46:4974 static RemoteTreeNode BuildTree(
75 syncer::UpdateResponseData update,
76 size_t max_depth,
77 UpdatesPerParentGUID* updates_per_parent_guid);
Raphael Kubo da Costacdf3e812019-12-16 11:39:1178
79 ~RemoteTreeNode();
80
81 // Allow moves, useful during construction.
82 RemoteTreeNode(RemoteTreeNode&&);
83 RemoteTreeNode& operator=(RemoteTreeNode&&);
84
Sergey Talantovff7224b2020-01-09 10:50:0785 const syncer::EntityData& entity() const { return update_.entity; }
86 int64_t response_version() const { return update_.response_version; }
Raphael Kubo da Costacdf3e812019-12-16 11:39:1187
88 // Direct children nodes, sorted by ascending unique position. These are
89 // guaranteed to be valid updates (e.g. IsValidBookmarkSpecifics()).
90 const std::vector<RemoteTreeNode>& children() const { return children_; }
91
92 // Recursively emplaces all GUIDs (this node and descendants) into
93 // |*guid_to_remote_node_map|, which must not be null.
94 void EmplaceSelfAndDescendantsByGUID(
Daniel Hosseinian6de24fa2020-11-17 19:19:5395 std::unordered_map<base::GUID, const RemoteTreeNode*, base::GUIDHash>*
Raphael Kubo da Costacdf3e812019-12-16 11:39:1196 guid_to_remote_node_map) const;
97
98 private:
99 static bool UniquePositionLessThan(const RemoteTreeNode& lhs,
100 const RemoteTreeNode& rhs);
101
102 RemoteTreeNode();
103
Sergey Talantovff7224b2020-01-09 10:50:07104 syncer::UpdateResponseData update_;
Mikel Astiza08d9662021-07-28 17:10:34105 // Redundant, parsed instance of the unique position in specifics, used
106 // to sort siblings by their position information.
107 syncer::UniquePosition unique_position_;
Raphael Kubo da Costacdf3e812019-12-16 11:39:11108 std::vector<RemoteTreeNode> children_;
109 };
Mikel Astiz8f5dad92019-11-19 19:15:52110
111 // A forest composed of multiple trees where the root of each tree represents
112 // a permanent node, keyed by server-defined unique tag of the root.
113 using RemoteForest = std::unordered_map<std::string, RemoteTreeNode>;
114
Mikel Astizafc87702019-11-25 10:53:45115 // Represents a pair of bookmarks, one local and one remote, that have been
116 // matched by GUID. They are guaranteed to have the same type and URL (if
117 // applicable).
118 struct GuidMatch {
119 const bookmarks::BookmarkNode* local_node;
120 const RemoteTreeNode* remote_node;
121 };
122
Mikel Astiz8f5dad92019-11-19 19:15:52123 // Constructs the remote bookmark tree to be merged. Each entry in the
124 // returned map is a permanent node, identified (keyed) by the server-defined
125 // tag. All invalid updates are filtered out, including invalid bookmark
126 // specifics as well as tombstones, in the unlikely event that the server
127 // sends tombstones as part of the initial download.
Mikel Astiz0bc9b162021-11-18 14:52:46128 // |tracker_for_recording_ignored_updates| must not be null and is exclusively
129 // used to record which updates where ignored because their parent couldn't be
130 // determined.
131 static RemoteForest BuildRemoteForest(
132 syncer::UpdateResponseDataList updates,
133 SyncedBookmarkTracker* tracker_for_recording_ignored_updates);
Mikel Astiz8f5dad92019-11-19 19:15:52134
Mikel Astiz4e0afd62021-05-12 13:50:49135 // Recursively counts and returns the number of descendants for |node|,
136 // excluding |node| itself.
137 static int CountRemoteTreeNodeDescendantsForUma(
138 const BookmarkModelMerger::RemoteTreeNode& node);
139
Mikel Astizafc87702019-11-25 10:53:45140 // Computes bookmark pairs that should be matched by GUID. Local bookmark
141 // GUIDs may be regenerated for the case where they collide with a remote GUID
142 // that is not compatible (e.g. folder vs non-folder).
Daniel Hosseinian6de24fa2020-11-17 19:19:53143 static std::unordered_map<base::GUID, GuidMatch, base::GUIDHash>
Mikel Astizafc87702019-11-25 10:53:45144 FindGuidMatchesOrReassignLocal(const RemoteForest& remote_forest,
145 bookmarks::BookmarkModel* bookmark_model);
Mikel Astiz8f5dad92019-11-19 19:15:52146
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06147 // Merges a local and a remote subtrees. The input nodes are two equivalent
148 // local and remote nodes. This method tries to recursively match their
149 // children. It updates the |bookmark_tracker_| accordingly.
150 void MergeSubtree(const bookmarks::BookmarkNode* local_node,
Mikel Astiz8f5dad92019-11-19 19:15:52151 const RemoteTreeNode& remote_node);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06152
Mikel Astiz8f5dad92019-11-19 19:15:52153 // Updates |local_node| to hold same GUID and semantics as its |remote_node|
Pauline Leitaod2928682019-09-21 15:26:46154 // match. The input nodes are two equivalent local and remote bookmarks that
155 // are about to be merged. The output node is the potentially replaced
156 // |local_node|. |local_node| must not be a BookmarkPermanentNode.
157 const bookmarks::BookmarkNode* UpdateBookmarkNodeFromSpecificsIncludingGUID(
158 const bookmarks::BookmarkNode* local_node,
Mikel Astiz8f5dad92019-11-19 19:15:52159 const RemoteTreeNode& remote_node);
Pauline Leitaod2928682019-09-21 15:26:46160
Mikel Astiz8f5dad92019-11-19 19:15:52161 // Creates a local bookmark node for a |remote_node|. The local node is
Pauline Leitaod2928682019-09-21 15:26:46162 // created under |local_parent| at position |index|. If the remote node has
163 // children, this method recursively creates them as well. It updates the
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06164 // |bookmark_tracker_| accordingly.
Mikel Astiz8f5dad92019-11-19 19:15:52165 void ProcessRemoteCreation(const RemoteTreeNode& remote_node,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06166 const bookmarks::BookmarkNode* local_parent,
Peter Kasting8cf4c23e2019-06-06 00:38:30167 size_t index);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06168
169 // Creates a server counter-part for the local node at position |index|
170 // under |parent|. If the local node has children, corresponding server nodes
171 // are created recursively. It updates the |bookmark_tracker_| accordingly and
172 // new nodes are marked to be committed.
Peter Kasting8cf4c23e2019-06-06 00:38:30173 void ProcessLocalCreation(const bookmarks::BookmarkNode* parent,
174 size_t index);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06175
Mikel Astiz8f5dad92019-11-19 19:15:52176 // Looks for a local node under |local_parent| that matches |remote_node|,
177 // starting at index |local_child_start_index|. First attempts to find a match
178 // by GUID and otherwise attempts to find one by semantics. If no match is
179 // found, a nullptr is returned.
Pauline Leitaod2928682019-09-21 15:26:46180 const bookmarks::BookmarkNode* FindMatchingLocalNode(
Mikel Astiz8f5dad92019-11-19 19:15:52181 const RemoteTreeNode& remote_node,
Pauline Leitaod2928682019-09-21 15:26:46182 const bookmarks::BookmarkNode* local_parent,
183 size_t local_child_start_index) const;
184
185 // If |local_node| has a remote counterpart of the same GUID, returns the
Mikel Astiz8f5dad92019-11-19 19:15:52186 // corresponding remote node, otherwise returns a nullptr. |local_node| must
Pauline Leitaod2928682019-09-21 15:26:46187 // not be null.
Mikel Astiz8f5dad92019-11-19 19:15:52188 const RemoteTreeNode* FindMatchingRemoteNodeByGUID(
Pauline Leitaod2928682019-09-21 15:26:46189 const bookmarks::BookmarkNode* local_node) const;
190
Mikel Astiz8f5dad92019-11-19 19:15:52191 // If |remote_node| has a local counterpart of the same GUID, returns the
192 // corresponding local node, otherwise returns a nullptr.
Pauline Leitaod2928682019-09-21 15:26:46193 const bookmarks::BookmarkNode* FindMatchingLocalNodeByGUID(
Mikel Astiz8f5dad92019-11-19 19:15:52194 const RemoteTreeNode& remote_node) const;
Pauline Leitaod2928682019-09-21 15:26:46195
196 // Tries to find a child local node under |local_parent| that matches
197 // |remote_node| semantically and returns the index of that node, as long as
198 // this local child cannot be matched by GUID to a different node. Matching is
199 // decided using NodeSemanticsMatch(). It searches in the children list
200 // starting from position |search_starting_child_index|. In case of no match
201 // is found, it returns |kInvalidIndex|.
202 size_t FindMatchingChildBySemanticsStartingAt(
Mikel Astiz8f5dad92019-11-19 19:15:52203 const RemoteTreeNode& remote_node,
Pauline Leitaod2928682019-09-21 15:26:46204 const bookmarks::BookmarkNode* local_parent,
205 size_t starting_child_index) const;
206
Rushan Suleymanov84c5295c2020-04-10 08:01:47207 // Used to generate a unique position for the current locally created
208 // bookmark.
209 syncer::UniquePosition GenerateUniquePositionForLocalCreation(
210 const bookmarks::BookmarkNode* parent,
211 size_t index,
212 const std::string& suffix) const;
213
Keishi Hattori0e45c022021-11-27 09:25:52214 const raw_ptr<bookmarks::BookmarkModel> bookmark_model_;
215 const raw_ptr<favicon::FaviconService> favicon_service_;
216 const raw_ptr<SyncedBookmarkTracker> bookmark_tracker_;
Mikel Astiz8f5dad92019-11-19 19:15:52217 // Preprocessed remote nodes in the form a forest where each tree's root is a
218 // permanent node. Computed upon construction via BuildRemoteForest().
219 const RemoteForest remote_forest_;
Daniel Hosseinian6de24fa2020-11-17 19:19:53220 std::unordered_map<base::GUID, GuidMatch, base::GUIDHash> guid_to_match_map_;
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06221};
222
223} // namespace sync_bookmarks
224
225#endif // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_