blob: ac289daeecc77987d810bb7a757f2c936be7a2f8 [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
14#include "base/macros.h"
Rushan Suleymanov84c5295c2020-04-10 08:01:4715#include "components/sync/base/unique_position.h"
Victor Hugo Vianna Silva722d50f2020-10-13 16:58:1216#include "components/sync/engine/commit_and_get_updates_types.h"
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0617
18namespace bookmarks {
19class BookmarkModel;
20class BookmarkNode;
21} // namespace bookmarks
22
Mohamed Amir Yosef7266a9a2018-08-13 14:38:3323namespace favicon {
24class FaviconService;
25} // namespace favicon
26
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0627namespace sync_bookmarks {
28
29class SyncedBookmarkTracker;
30
31// Responsible for merging local and remote bookmark models when bookmark sync
32// is enabled for the first time by the user (i.e. no sync metadata exists
33// locally), so we need a best-effort merge based on similarity. It implements
34// similar logic to that in BookmarkModelAssociator::AssociateModels() to be
35// used by the BookmarkModelTypeProcessor().
36class 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
45 ~BookmarkModelMerger();
46
Mikel Astiz8f5dad92019-11-19 19:15:5247 // Merges the remote bookmark model represented as the updates received from
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0648 // the sync server and local bookmark model |bookmark_model|, and updates the
49 // model and |bookmark_tracker| (all of which are injected in the constructor)
50 // accordingly. On return, there will be a 1:1 mapping between bookmark nodes
51 // and metadata entities in the injected tracker.
52 void Merge();
53
Rushan Suleymanov6f651f12020-06-19 16:25:3654 size_t valid_updates_without_full_title_for_uma() const {
55 return valid_updates_without_full_title_;
56 }
57
Mohamed Amir Yosefa89b53f62018-08-02 20:17:0658 private:
Mikel Astiz8f5dad92019-11-19 19:15:5259 // Internal representation of a remote tree, composed of nodes.
Raphael Kubo da Costacdf3e812019-12-16 11:39:1160 class RemoteTreeNode final {
61 private:
62 using UpdatesPerParentId =
Rushan Suleymanovc414e152020-05-19 21:26:5463 std::unordered_map<std::string, std::list<syncer::UpdateResponseData>>;
Raphael Kubo da Costacdf3e812019-12-16 11:39:1164
65 public:
66 // Constructs a tree given |update| as root and recursively all descendants
67 // by traversing |*updates_per_parent_id|. |update| and
68 // |updates_per_parent_id| must not be null. All updates
69 // |*updates_per_parent_id| must represent valid updates. Updates
70 // corresponding from descendant nodes are moved away from
Chris Davis96a12c72020-06-26 17:53:3671 // |*updates_per_parent_id|. |max_depth| is the max tree depth to sync
72 // after which content is silently ignored.
Sergey Talantovff7224b2020-01-09 10:50:0773 static RemoteTreeNode BuildTree(syncer::UpdateResponseData update,
Chris Davis96a12c72020-06-26 17:53:3674 size_t max_depth,
Sergey Talantovff7224b2020-01-09 10:50:0775 UpdatesPerParentId* updates_per_parent_id);
Raphael Kubo da Costacdf3e812019-12-16 11:39:1176
77 ~RemoteTreeNode();
78
79 // Allow moves, useful during construction.
80 RemoteTreeNode(RemoteTreeNode&&);
81 RemoteTreeNode& operator=(RemoteTreeNode&&);
82
Sergey Talantovff7224b2020-01-09 10:50:0783 const syncer::EntityData& entity() const { return update_.entity; }
84 int64_t response_version() const { return update_.response_version; }
Raphael Kubo da Costacdf3e812019-12-16 11:39:1185
86 // Direct children nodes, sorted by ascending unique position. These are
87 // guaranteed to be valid updates (e.g. IsValidBookmarkSpecifics()).
88 const std::vector<RemoteTreeNode>& children() const { return children_; }
89
90 // Recursively emplaces all GUIDs (this node and descendants) into
91 // |*guid_to_remote_node_map|, which must not be null.
92 void EmplaceSelfAndDescendantsByGUID(
93 std::unordered_map<std::string, const RemoteTreeNode*>*
94 guid_to_remote_node_map) const;
95
96 private:
97 static bool UniquePositionLessThan(const RemoteTreeNode& lhs,
98 const RemoteTreeNode& rhs);
99
100 RemoteTreeNode();
101
Sergey Talantovff7224b2020-01-09 10:50:07102 syncer::UpdateResponseData update_;
Raphael Kubo da Costacdf3e812019-12-16 11:39:11103 std::vector<RemoteTreeNode> children_;
104 };
Mikel Astiz8f5dad92019-11-19 19:15:52105
106 // A forest composed of multiple trees where the root of each tree represents
107 // a permanent node, keyed by server-defined unique tag of the root.
108 using RemoteForest = std::unordered_map<std::string, RemoteTreeNode>;
109
Mikel Astizafc87702019-11-25 10:53:45110 // Represents a pair of bookmarks, one local and one remote, that have been
111 // matched by GUID. They are guaranteed to have the same type and URL (if
112 // applicable).
113 struct GuidMatch {
114 const bookmarks::BookmarkNode* local_node;
115 const RemoteTreeNode* remote_node;
116 };
117
Mikel Astiz8f5dad92019-11-19 19:15:52118 // Constructs the remote bookmark tree to be merged. Each entry in the
119 // returned map is a permanent node, identified (keyed) by the server-defined
120 // tag. All invalid updates are filtered out, including invalid bookmark
121 // specifics as well as tombstones, in the unlikely event that the server
122 // sends tombstones as part of the initial download.
123 static RemoteForest BuildRemoteForest(syncer::UpdateResponseDataList updates);
124
Mikel Astizafc87702019-11-25 10:53:45125 // Computes bookmark pairs that should be matched by GUID. Local bookmark
126 // GUIDs may be regenerated for the case where they collide with a remote GUID
127 // that is not compatible (e.g. folder vs non-folder).
128 static std::unordered_map<std::string, GuidMatch>
129 FindGuidMatchesOrReassignLocal(const RemoteForest& remote_forest,
130 bookmarks::BookmarkModel* bookmark_model);
Mikel Astiz8f5dad92019-11-19 19:15:52131
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06132 // Merges a local and a remote subtrees. The input nodes are two equivalent
133 // local and remote nodes. This method tries to recursively match their
134 // children. It updates the |bookmark_tracker_| accordingly.
135 void MergeSubtree(const bookmarks::BookmarkNode* local_node,
Mikel Astiz8f5dad92019-11-19 19:15:52136 const RemoteTreeNode& remote_node);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06137
Mikel Astiz8f5dad92019-11-19 19:15:52138 // Updates |local_node| to hold same GUID and semantics as its |remote_node|
Pauline Leitaod2928682019-09-21 15:26:46139 // match. The input nodes are two equivalent local and remote bookmarks that
140 // are about to be merged. The output node is the potentially replaced
141 // |local_node|. |local_node| must not be a BookmarkPermanentNode.
142 const bookmarks::BookmarkNode* UpdateBookmarkNodeFromSpecificsIncludingGUID(
143 const bookmarks::BookmarkNode* local_node,
Mikel Astiz8f5dad92019-11-19 19:15:52144 const RemoteTreeNode& remote_node);
Pauline Leitaod2928682019-09-21 15:26:46145
Mikel Astiz8f5dad92019-11-19 19:15:52146 // Creates a local bookmark node for a |remote_node|. The local node is
Pauline Leitaod2928682019-09-21 15:26:46147 // created under |local_parent| at position |index|. If the remote node has
148 // children, this method recursively creates them as well. It updates the
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06149 // |bookmark_tracker_| accordingly.
Mikel Astiz8f5dad92019-11-19 19:15:52150 void ProcessRemoteCreation(const RemoteTreeNode& remote_node,
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06151 const bookmarks::BookmarkNode* local_parent,
Peter Kasting8cf4c23e2019-06-06 00:38:30152 size_t index);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06153
154 // Creates a server counter-part for the local node at position |index|
155 // under |parent|. If the local node has children, corresponding server nodes
156 // are created recursively. It updates the |bookmark_tracker_| accordingly and
157 // new nodes are marked to be committed.
Peter Kasting8cf4c23e2019-06-06 00:38:30158 void ProcessLocalCreation(const bookmarks::BookmarkNode* parent,
159 size_t index);
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06160
Mikel Astiz8f5dad92019-11-19 19:15:52161 // Looks for a local node under |local_parent| that matches |remote_node|,
162 // starting at index |local_child_start_index|. First attempts to find a match
163 // by GUID and otherwise attempts to find one by semantics. If no match is
164 // found, a nullptr is returned.
Pauline Leitaod2928682019-09-21 15:26:46165 const bookmarks::BookmarkNode* FindMatchingLocalNode(
Mikel Astiz8f5dad92019-11-19 19:15:52166 const RemoteTreeNode& remote_node,
Pauline Leitaod2928682019-09-21 15:26:46167 const bookmarks::BookmarkNode* local_parent,
168 size_t local_child_start_index) const;
169
170 // If |local_node| has a remote counterpart of the same GUID, returns the
Mikel Astiz8f5dad92019-11-19 19:15:52171 // corresponding remote node, otherwise returns a nullptr. |local_node| must
Pauline Leitaod2928682019-09-21 15:26:46172 // not be null.
Mikel Astiz8f5dad92019-11-19 19:15:52173 const RemoteTreeNode* FindMatchingRemoteNodeByGUID(
Pauline Leitaod2928682019-09-21 15:26:46174 const bookmarks::BookmarkNode* local_node) const;
175
Mikel Astiz8f5dad92019-11-19 19:15:52176 // If |remote_node| has a local counterpart of the same GUID, returns the
177 // corresponding local node, otherwise returns a nullptr.
Pauline Leitaod2928682019-09-21 15:26:46178 const bookmarks::BookmarkNode* FindMatchingLocalNodeByGUID(
Mikel Astiz8f5dad92019-11-19 19:15:52179 const RemoteTreeNode& remote_node) const;
Pauline Leitaod2928682019-09-21 15:26:46180
181 // Tries to find a child local node under |local_parent| that matches
182 // |remote_node| semantically and returns the index of that node, as long as
183 // this local child cannot be matched by GUID to a different node. Matching is
184 // decided using NodeSemanticsMatch(). It searches in the children list
185 // starting from position |search_starting_child_index|. In case of no match
186 // is found, it returns |kInvalidIndex|.
187 size_t FindMatchingChildBySemanticsStartingAt(
Mikel Astiz8f5dad92019-11-19 19:15:52188 const RemoteTreeNode& remote_node,
Pauline Leitaod2928682019-09-21 15:26:46189 const bookmarks::BookmarkNode* local_parent,
190 size_t starting_child_index) const;
191
Rushan Suleymanov84c5295c2020-04-10 08:01:47192 // Used to generate a unique position for the current locally created
193 // bookmark.
194 syncer::UniquePosition GenerateUniquePositionForLocalCreation(
195 const bookmarks::BookmarkNode* parent,
196 size_t index,
197 const std::string& suffix) const;
198
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06199 bookmarks::BookmarkModel* const bookmark_model_;
Mohamed Amir Yosef7266a9a2018-08-13 14:38:33200 favicon::FaviconService* const favicon_service_;
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06201 SyncedBookmarkTracker* const bookmark_tracker_;
Mikel Astiz8f5dad92019-11-19 19:15:52202 // Preprocessed remote nodes in the form a forest where each tree's root is a
203 // permanent node. Computed upon construction via BuildRemoteForest().
204 const RemoteForest remote_forest_;
Mikel Astizafc87702019-11-25 10:53:45205 std::unordered_map<std::string, GuidMatch> guid_to_match_map_;
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06206
Rushan Suleymanov6f651f12020-06-19 16:25:36207 size_t valid_updates_without_full_title_ = 0;
208
Mohamed Amir Yosefa89b53f62018-08-02 20:17:06209 DISALLOW_COPY_AND_ASSIGN(BookmarkModelMerger);
210};
211
212} // namespace sync_bookmarks
213
214#endif // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_