Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(403)

Side by Side Diff: chrome/browser/sync/glue/model_associator.cc

Issue 160542: Rolling back 22317 (Closed) Base URL: svn://chrome-svn.corp.google.com/chrome/trunk/src/
Patch Set: Created 11 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2006-2009 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 #ifdef CHROME_PERSONALIZATION
6
7 #include "chrome/browser/sync/glue/model_associator.h"
8
9 #include <stack>
10
11 #include "base/message_loop.h"
12 #include "base/task.h"
13 #include "chrome/browser/bookmarks/bookmark_model.h"
14 #include "chrome/browser/sync/engine/syncapi.h"
15 #include "chrome/browser/sync/profile_sync_service.h"
16
17 namespace browser_sync {
18
19 // The sync protocol identifies top-level entities by means of well-known tags,
20 // which should not be confused with titles. Each tag corresponds to a
21 // singleton instance of a particular top-level node in a user's share; the
22 // tags are consistent across users. The tags allow us to locate the specific
23 // folders whose contents we care about synchronizing, without having to do a
24 // lookup by name or path. The tags should not be made user-visible.
25 // For example, the tag "bookmark_bar" represents the permanent node for
26 // bookmarks bar in Chrome. The tag "other_bookmarks" represents the permanent
27 // folder Other Bookmarks in Chrome.
28 //
29 // It is the responsibility of something upstream (at time of writing,
30 // the sync server) to create these tagged nodes when initializing sync
31 // for the first time for a user. Thus, once the backend finishes
32 // initializing, the ProfileSyncService can rely on the presence of tagged
33 // nodes.
34 //
35 // TODO(ncarter): Pull these tags from an external protocol specification
36 // rather than hardcoding them here.
37 static const wchar_t* kOtherBookmarksTag = L"other_bookmarks";
38 static const wchar_t* kBookmarkBarTag = L"bookmark_bar";
39
40 // Bookmark comparer for map of bookmark nodes.
41 class BookmarkComparer {
42 public:
43 // Compares the two given nodes and returns whether node1 should appear
44 // before node2 in strict weak ordering.
45 bool operator()(const BookmarkNode* node1,
46 const BookmarkNode* node2) const {
47 DCHECK(node1);
48 DCHECK(node2);
49
50 // Keep folder nodes before non-folder nodes.
51 if (node1->is_folder() != node2->is_folder())
52 return node1->is_folder();
53
54 int result = node1->GetTitle().compare(node2->GetTitle());
55 if (result != 0)
56 return result < 0;
57
58 result = node1->GetURL().spec().compare(node2->GetURL().spec());
59 if (result != 0)
60 return result < 0;
61
62 return false;
63 }
64 };
65
66 // Provides the following abstraction: given a parent bookmark node, find best
67 // matching child node for many sync nodes.
68 class BookmarkNodeFinder {
69 public:
70 // Creats an instance with the given parent bookmark node.
71 explicit BookmarkNodeFinder(const BookmarkNode* parent_node);
72
73 // Finds best matching node for the given sync node.
74 // Returns the matching node if one exists; NULL otherwise. If a matching
75 // node is found, it's removed for further matches.
76 const BookmarkNode* FindBookmarkNode(const sync_api::BaseNode& sync_node);
77
78 private:
79 typedef std::set<const BookmarkNode*, BookmarkComparer> BookmarkNodesSet;
80
81 const BookmarkNode* parent_node_;
82 BookmarkNodesSet child_nodes_;
83
84 DISALLOW_COPY_AND_ASSIGN(BookmarkNodeFinder);
85 };
86
87 BookmarkNodeFinder::BookmarkNodeFinder(const BookmarkNode* parent_node)
88 : parent_node_(parent_node) {
89 for (int i = 0; i < parent_node_->GetChildCount(); ++i)
90 child_nodes_.insert(parent_node_->GetChild(i));
91 }
92
93 const BookmarkNode* BookmarkNodeFinder::FindBookmarkNode(
94 const sync_api::BaseNode& sync_node) {
95 // Create a bookmark node from the given sync node.
96 BookmarkNode temp_node(GURL(sync_node.GetURL()));
97 temp_node.SetTitle(UTF16ToWide(sync_node.GetTitle()));
98 if (sync_node.GetIsFolder())
99 temp_node.SetType(BookmarkNode::FOLDER);
100 else
101 temp_node.SetType(BookmarkNode::URL);
102
103 const BookmarkNode* result = NULL;
104 BookmarkNodesSet::iterator iter = child_nodes_.find(&temp_node);
105 if (iter != child_nodes_.end()) {
106 result = *iter;
107 // Remove the matched node so we don't match with it again.
108 child_nodes_.erase(iter);
109 }
110
111 return result;
112 }
113
114 ModelAssociator::ModelAssociator(ProfileSyncService* sync_service)
115 : sync_service_(sync_service),
116 task_pending_(false) {
117 DCHECK(sync_service_);
118 }
119
120 void ModelAssociator::ClearAll() {
121 id_map_.clear();
122 id_map_inverse_.clear();
123 dirty_assocations_sync_ids_.clear();
124 }
125
126 int64 ModelAssociator::GetSyncIdFromBookmarkId(int64 node_id) const {
127 BookmarkIdToSyncIdMap::const_iterator iter = id_map_.find(node_id);
128 return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
129 }
130
131 bool ModelAssociator::GetBookmarkIdFromSyncId(int64 sync_id,
132 int64* node_id) const {
133 SyncIdToBookmarkIdMap::const_iterator iter = id_map_inverse_.find(sync_id);
134 if (iter == id_map_inverse_.end())
135 return false;
136 *node_id = iter->second;
137 return true;
138 }
139
140 bool ModelAssociator::InitSyncNodeFromBookmarkId(
141 int64 node_id,
142 sync_api::BaseNode* sync_node) {
143 DCHECK(sync_node);
144 int64 sync_id = GetSyncIdFromBookmarkId(node_id);
145 if (sync_id == sync_api::kInvalidId)
146 return false;
147 if (!sync_node->InitByIdLookup(sync_id))
148 return false;
149 DCHECK(sync_node->GetId() == sync_id);
150 return true;
151 }
152
153 const BookmarkNode* ModelAssociator::GetBookmarkNodeFromSyncId(int64 sync_id) {
154 int64 node_id;
155 if (!GetBookmarkIdFromSyncId(sync_id, &node_id))
156 return false;
157 BookmarkModel* model = sync_service_->profile()->GetBookmarkModel();
158 return model->GetNodeByID(node_id);
159 }
160
161 void ModelAssociator::AssociateIds(int64 node_id, int64 sync_id) {
162 DCHECK_NE(sync_id, sync_api::kInvalidId);
163 DCHECK(id_map_.find(node_id) == id_map_.end());
164 DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
165 id_map_[node_id] = sync_id;
166 id_map_inverse_[sync_id] = node_id;
167 dirty_assocations_sync_ids_.insert(sync_id);
168 PostPersistAssociationsTask();
169 }
170
171 void ModelAssociator::DisassociateIds(int64 sync_id) {
172 SyncIdToBookmarkIdMap::iterator iter = id_map_inverse_.find(sync_id);
173 if (iter == id_map_inverse_.end())
174 return;
175 id_map_.erase(iter->second);
176 id_map_inverse_.erase(iter);
177 dirty_assocations_sync_ids_.erase(sync_id);
178 }
179
180 bool ModelAssociator::BookmarkModelHasUserCreatedNodes() const {
181 BookmarkModel* model = sync_service_->profile()->GetBookmarkModel();
182 DCHECK(model->IsLoaded());
183 return model->GetBookmarkBarNode()->GetChildCount() > 0 ||
184 model->other_node()->GetChildCount() > 0;
185 }
186
187 bool ModelAssociator::SyncModelHasUserCreatedNodes() {
188 int64 bookmark_bar_sync_id;
189 if (!GetSyncIdForTaggedNode(WideToUTF16(kBookmarkBarTag),
190 &bookmark_bar_sync_id)) {
191 NOTREACHED();
192 return false;
193 }
194 int64 other_bookmarks_sync_id;
195 if (!GetSyncIdForTaggedNode(WideToUTF16(kOtherBookmarksTag),
196 &other_bookmarks_sync_id)) {
197 NOTREACHED();
198 return false;
199 }
200
201 sync_api::ReadTransaction trans(
202 sync_service_->backend()->GetUserShareHandle());
203
204 sync_api::ReadNode bookmark_bar_node(&trans);
205 if (!bookmark_bar_node.InitByIdLookup(bookmark_bar_sync_id)) {
206 NOTREACHED();
207 return false;
208 }
209
210 sync_api::ReadNode other_bookmarks_node(&trans);
211 if (!other_bookmarks_node.InitByIdLookup(other_bookmarks_sync_id)) {
212 NOTREACHED();
213 return false;
214 }
215
216 // Sync model has user created nodes if either one of the permanent nodes
217 // has children.
218 return bookmark_bar_node.GetFirstChildId() != sync_api::kInvalidId ||
219 other_bookmarks_node.GetFirstChildId() != sync_api::kInvalidId;
220 }
221
222 bool ModelAssociator::NodesMatch(const BookmarkNode* bookmark,
223 const sync_api::BaseNode* sync_node) const {
224 if (bookmark->GetTitle() != UTF16ToWide(sync_node->GetTitle()))
225 return false;
226 if (bookmark->is_folder() != sync_node->GetIsFolder())
227 return false;
228 if (bookmark->is_url()) {
229 if (bookmark->GetURL() != GURL(sync_node->GetURL()))
230 return false;
231 }
232 // Don't compare favicons here, because they are not really
233 // user-updated and we don't have versioning information -- a site changing
234 // its favicon shouldn't result in a bookmark mismatch.
235 return true;
236 }
237
238 bool ModelAssociator::AssociateTaggedPermanentNode(
239 const BookmarkNode* permanent_node,
240 const string16 &tag) {
241 // Do nothing if |permanent_node| is already initialized and associated.
242 int64 sync_id = GetSyncIdFromBookmarkId(permanent_node->id());
243 if (sync_id != sync_api::kInvalidId)
244 return true;
245 if (!GetSyncIdForTaggedNode(tag, &sync_id))
246 return false;
247
248 AssociateIds(permanent_node->id(), sync_id);
249 return true;
250 }
251
252 bool ModelAssociator::GetSyncIdForTaggedNode(const string16& tag,
253 int64* sync_id) {
254 sync_api::ReadTransaction trans(
255 sync_service_->backend()->GetUserShareHandle());
256 sync_api::ReadNode sync_node(&trans);
257 if (!sync_node.InitByTagLookup(tag.c_str()))
258 return false;
259 *sync_id = sync_node.GetId();
260 return true;
261 }
262
263 bool ModelAssociator::AssociateModels() {
264 // Try to load model associations from persisted associations first. If that
265 // succeeds, we don't need to run the complex model matching algorithm.
266 if (LoadAssociations())
267 return true;
268
269 ClearAll();
270
271 // We couldn't load model assocations from persisted assocations. So build
272 // them.
273 return BuildAssocations();
274 }
275
276 bool ModelAssociator::BuildAssocations() {
277 // Algorithm description:
278 // Match up the roots and recursively do the following:
279 // * For each sync node for the current sync parent node, find the best
280 // matching bookmark node under the corresponding bookmark parent node.
281 // If no matching node is found, create a new bookmark node in the same
282 // position as the corresponding sync node.
283 // If a matching node is found, update the properties of it from the
284 // corresponding sync node.
285 // * When all children sync nodes are done, add the extra children bookmark
286 // nodes to the sync parent node.
287 //
288 // This algorithm will do a good job of merging when folder names are a good
289 // indicator of the two folders being the same. It will handle reordering and
290 // new node addition very well (without creating duplicates).
291 // This algorithm will not do well if the folder name has changes but the
292 // children under them are all the same.
293
294 BookmarkModel* model = sync_service_->profile()->GetBookmarkModel();
295 DCHECK(model->IsLoaded());
296
297 // To prime our association, we associate the top-level nodes, Bookmark Bar
298 // and Other Bookmarks.
299 if (!AssociateTaggedPermanentNode(model->other_node(),
300 WideToUTF16(kOtherBookmarksTag))) {
301 NOTREACHED() << "Server did not create top-level nodes. Possibly we "
302 << "are running against an out-of-date server?";
303 return false;
304 }
305 if (!AssociateTaggedPermanentNode(model->GetBookmarkBarNode(),
306 WideToUTF16(kBookmarkBarTag))) {
307 NOTREACHED() << "Server did not create top-level nodes. Possibly we "
308 << "are running against an out-of-date server?";
309 return false;
310 }
311 int64 bookmark_bar_sync_id = GetSyncIdFromBookmarkId(
312 model->GetBookmarkBarNode()->id());
313 DCHECK(bookmark_bar_sync_id != sync_api::kInvalidId);
314 int64 other_bookmarks_sync_id = GetSyncIdFromBookmarkId(
315 model->other_node()->id());
316 DCHECK(other_bookmarks_sync_id!= sync_api::kInvalidId);
317
318 std::stack<int64> dfs_stack;
319 dfs_stack.push(other_bookmarks_sync_id);
320 dfs_stack.push(bookmark_bar_sync_id);
321
322 sync_api::WriteTransaction trans(
323 sync_service_->backend()->GetUserShareHandle());
324
325 while (!dfs_stack.empty()) {
326 int64 sync_parent_id = dfs_stack.top();
327 dfs_stack.pop();
328
329 sync_api::ReadNode sync_parent(&trans);
330 if (!sync_parent.InitByIdLookup(sync_parent_id)) {
331 NOTREACHED();
332 return false;
333 }
334 // Only folder nodes are pushed on to the stack.
335 DCHECK(sync_parent.GetIsFolder());
336
337 const BookmarkNode* parent_node = GetBookmarkNodeFromSyncId(sync_parent_id);
338 DCHECK(parent_node->is_folder());
339
340 BookmarkNodeFinder node_finder(parent_node);
341
342 int index = 0;
343 int64 sync_child_id = sync_parent.GetFirstChildId();
344 while (sync_child_id != sync_api::kInvalidId) {
345 sync_api::WriteNode sync_child_node(&trans);
346 if (!sync_child_node.InitByIdLookup(sync_child_id)) {
347 NOTREACHED();
348 return false;
349 }
350
351 const BookmarkNode* child_node = NULL;
352 child_node = node_finder.FindBookmarkNode(sync_child_node);
353 if (child_node) {
354 model->Move(child_node, parent_node, index);
355 // Set the favicon for bookmark node from sync node or vice versa.
356 if (!sync_service_->SetBookmarkFavicon(&sync_child_node, child_node))
357 sync_service_->SetSyncNodeFavicon(child_node, &sync_child_node);
358 } else {
359 // Create a new bookmark node for the sync node.
360 child_node = sync_service_->CreateBookmarkNode(&sync_child_node,
361 parent_node,
362 index);
363 }
364 AssociateIds(child_node->id(), sync_child_id);
365 if (sync_child_node.GetIsFolder())
366 dfs_stack.push(sync_child_id);
367
368 sync_child_id = sync_child_node.GetSuccessorId();
369 ++index;
370 }
371
372 // At this point all the children nodes of the parent sync node have
373 // corresponding children in the parent bookmark node and they are all in
374 // the right positions: from 0 to index - 1.
375 // So the children starting from index in the parent bookmark node are the
376 // ones that are not present in the parent sync node. So create them.
377 for (int i = index; i < parent_node->GetChildCount(); ++i) {
378 sync_child_id = sync_service_->CreateSyncNode(parent_node, i, &trans);
379 if (parent_node->GetChild(i)->is_folder())
380 dfs_stack.push(sync_child_id);
381 }
382 }
383 return true;
384 }
385
386 void ModelAssociator::PostPersistAssociationsTask() {
387 // No need to post a task if a task is already pending.
388 if (task_pending_)
389 return;
390 task_pending_ = true;
391 MessageLoop::current()->PostTask(
392 FROM_HERE,
393 NewRunnableMethod(this, &ModelAssociator::PersistAssociations));
394 }
395
396 void ModelAssociator::PersistAssociations() {
397 DCHECK(task_pending_);
398 task_pending_ = false;
399
400 // If there are no dirty assocations we have nothing to do. We handle this
401 // explicity instead of letting the for loop do it to avoid creating a write
402 // transaction in this case.
403 if (dirty_assocations_sync_ids_.empty()) {
404 DCHECK(id_map_.empty());
405 DCHECK(id_map_inverse_.empty());
406 return;
407 }
408
409 sync_api::WriteTransaction trans(
410 sync_service_->backend()->GetUserShareHandle());
411 DirtyAssocationsSyncIds::iterator iter;
412 for (iter = dirty_assocations_sync_ids_.begin();
413 iter != dirty_assocations_sync_ids_.end();
414 ++iter) {
415 int64 sync_id = *iter;
416 sync_api::WriteNode sync_node(&trans);
417 if (!sync_node.InitByIdLookup(sync_id)) {
418 sync_service_->SetUnrecoverableError();
419 return;
420 }
421 int64 node_id;
422 if (GetBookmarkIdFromSyncId(sync_id, &node_id))
423 sync_node.SetExternalId(node_id);
424 else
425 NOTREACHED();
426 }
427 dirty_assocations_sync_ids_.clear();
428 }
429
430 bool ModelAssociator::LoadAssociations() {
431 BookmarkModel* model = sync_service_->profile()->GetBookmarkModel();
432 DCHECK(model->IsLoaded());
433 // If the bookmarks changed externally, our previous assocations may not be
434 // valid; so return false.
435 if (model->file_changed())
436 return false;
437
438 // Our persisted assocations should be valid. Try to populate id assocation
439 // maps using persisted assocations.
440
441 int64 other_bookmarks_id;
442 if (!GetSyncIdForTaggedNode(WideToUTF16(kOtherBookmarksTag),
443 &other_bookmarks_id)) {
444 NOTREACHED(); // We should always be able to find the permanent nodes.
445 return false;
446 }
447 int64 bookmark_bar_id;
448 if (!GetSyncIdForTaggedNode(WideToUTF16(kBookmarkBarTag), &bookmark_bar_id)) {
449 NOTREACHED(); // We should always be able to find the permanent nodes.
450 return false;
451 }
452
453 std::stack<int64> dfs_stack;
454 dfs_stack.push(other_bookmarks_id);
455 dfs_stack.push(bookmark_bar_id);
456
457 sync_api::ReadTransaction trans(
458 sync_service_->backend()->GetUserShareHandle());
459
460 while (!dfs_stack.empty()) {
461 int64 parent_id = dfs_stack.top();
462 dfs_stack.pop();
463 sync_api::ReadNode sync_parent(&trans);
464 if (!sync_parent.InitByIdLookup(parent_id)) {
465 NOTREACHED();
466 return false;
467 }
468
469 int64 external_id = sync_parent.GetExternalId();
470 if (external_id == 0)
471 return false;
472
473 const BookmarkNode* node = model->GetNodeByID(external_id);
474 if (!node)
475 return false;
476
477 // Don't try to call NodesMatch on permanent nodes like bookmark bar and
478 // other bookmarks. They are not expected to match.
479 if (node != model->GetBookmarkBarNode() &&
480 node != model->other_node() &&
481 !NodesMatch(node, &sync_parent))
482 return false;
483
484 AssociateIds(external_id, sync_parent.GetId());
485
486 // Add all children of the current node to the stack.
487 int64 child_id = sync_parent.GetFirstChildId();
488 while (child_id != sync_api::kInvalidId) {
489 dfs_stack.push(child_id);
490 sync_api::ReadNode child_node(&trans);
491 if (!child_node.InitByIdLookup(child_id)) {
492 NOTREACHED();
493 return false;
494 }
495 child_id = child_node.GetSuccessorId();
496 }
497 }
498 DCHECK(dfs_stack.empty());
499 return true;
500 }
501
502 } // namespace browser_sync
503
504 #endif // CHROME_PERSONALIZATION
OLDNEW
« no previous file with comments | « chrome/browser/sync/glue/model_associator.h ('k') | chrome/browser/sync/glue/sync_backend_host.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698