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
3 // found in the LICENSE file.
5 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
11 #include "base/location.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/bookmarks/bookmark_model.h"
17 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
18 #include "chrome/browser/bookmarks/bookmark_utils.h"
19 #include "chrome/browser/favicon/favicon_service.h"
20 #include "chrome/browser/favicon/favicon_service_factory.h"
21 #include "chrome/browser/history/history_service.h"
22 #include "chrome/browser/history/history_service_factory.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/sync/profile_sync_service.h"
25 #include "chrome/browser/undo/bookmark_undo_service.h"
26 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
27 #include "chrome/browser/undo/undo_manager_utils.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "sync/internal_api/public/change_record.h"
30 #include "sync/internal_api/public/read_node.h"
31 #include "sync/internal_api/public/write_node.h"
32 #include "sync/internal_api/public/write_transaction.h"
33 #include "sync/syncable/entry.h" // TODO(tim): Investigating bug 121587.
34 #include "sync/syncable/syncable_write_transaction.h"
35 #include "ui/gfx/favicon_size.h"
36 #include "ui/gfx/image/image_util.h"
38 using content::BrowserThread
;
39 using syncer::ChangeRecord
;
40 using syncer::ChangeRecordList
;
42 namespace browser_sync
{
44 static const char kMobileBookmarksTag
[] = "synced_bookmarks";
46 BookmarkChangeProcessor::BookmarkChangeProcessor(
47 BookmarkModelAssociator
* model_associator
,
48 DataTypeErrorHandler
* error_handler
)
49 : ChangeProcessor(error_handler
),
50 bookmark_model_(NULL
),
51 model_associator_(model_associator
) {
52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
53 DCHECK(model_associator
);
54 DCHECK(error_handler
);
57 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
59 bookmark_model_
->RemoveObserver(this);
62 void BookmarkChangeProcessor::StartImpl(Profile
* profile
) {
63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
66 DCHECK(!bookmark_model_
);
67 bookmark_model_
= BookmarkModelFactory::GetForProfile(profile
);
68 DCHECK(bookmark_model_
->loaded());
69 bookmark_model_
->AddObserver(this);
72 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
73 const BookmarkNode
* src
,
75 syncer::WriteNode
* dst
) {
76 // Set the properties of the item.
77 dst
->SetIsFolder(src
->is_folder());
78 dst
->SetTitle(base::UTF16ToWideHack(src
->GetTitle()));
79 sync_pb::BookmarkSpecifics
bookmark_specifics(dst
->GetBookmarkSpecifics());
80 if (!src
->is_folder())
81 bookmark_specifics
.set_url(src
->url().spec());
82 bookmark_specifics
.set_creation_time_us(src
->date_added().ToInternalValue());
83 dst
->SetBookmarkSpecifics(bookmark_specifics
);
84 SetSyncNodeFavicon(src
, model
, dst
);
85 SetSyncNodeMetaInfo(src
, dst
);
89 void BookmarkChangeProcessor::EncodeFavicon(
90 const BookmarkNode
* src
,
92 scoped_refptr
<base::RefCountedMemory
>* dst
) {
93 const gfx::Image
& favicon
= model
->GetFavicon(src
);
95 // Check for empty images. This can happen if the favicon is
96 // still being loaded.
97 if (favicon
.IsEmpty())
100 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
102 *dst
= favicon
.As1xPNGBytes();
105 void BookmarkChangeProcessor::RemoveOneSyncNode(syncer::WriteNode
* sync_node
) {
106 // This node should have no children.
107 DCHECK(!sync_node
->HasChildren());
108 // Remove association and delete the sync node.
109 model_associator_
->Disassociate(sync_node
->GetId());
110 sync_node
->Tombstone();
113 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
114 const BookmarkNode
* topmost
) {
116 syncer::syncable::kInvalidTransactionVersion
;
118 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
119 syncer::WriteNode
topmost_sync_node(&trans
);
120 if (!model_associator_
->InitSyncNodeFromChromeId(topmost
->id(),
121 &topmost_sync_node
)) {
122 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
126 // Check that |topmost| has been unlinked.
127 DCHECK(topmost
->is_root());
128 RemoveAllChildNodes(&trans
, topmost
->id());
129 // Remove the node itself.
130 RemoveOneSyncNode(&topmost_sync_node
);
133 // Don't need to update versions of deleted nodes.
134 UpdateTransactionVersion(new_version
, bookmark_model_
,
135 std::vector
<const BookmarkNode
*>());
138 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
139 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
141 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
143 RemoveAllChildNodes(&trans
, bookmark_model_
->bookmark_bar_node()->id());
144 RemoveAllChildNodes(&trans
, bookmark_model_
->other_node()->id());
145 // Remove mobile bookmarks node only if it is present.
146 const int64 mobile_bookmark_id
= bookmark_model_
->mobile_node()->id();
147 if (model_associator_
->GetSyncIdFromChromeId(mobile_bookmark_id
) !=
148 syncer::kInvalidId
) {
149 RemoveAllChildNodes(&trans
, bookmark_model_
->mobile_node()->id());
153 // Don't need to update versions of deleted nodes.
154 UpdateTransactionVersion(new_version
, bookmark_model_
,
155 std::vector
<const BookmarkNode
*>());
158 void BookmarkChangeProcessor::RemoveAllChildNodes(
159 syncer::WriteTransaction
* trans
, const int64
& topmost_node_id
) {
160 syncer::WriteNode
topmost_node(trans
);
161 if (!model_associator_
->InitSyncNodeFromChromeId(topmost_node_id
,
163 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
167 const int64 topmost_sync_id
= topmost_node
.GetId();
169 // Do a DFS and delete all the child sync nodes, use sync id instead of
170 // bookmark node ids since the bookmark nodes may already be deleted.
171 // The equivalent recursive version of this iterative DFS:
172 // remove_all_children(node_id, topmost_node_id):
173 // node.initByIdLookup(node_id)
174 // while(node.GetFirstChildId() != syncer::kInvalidId)
175 // remove_all_children(node.GetFirstChildId(), topmost_node_id)
176 // if(node_id != topmost_node_id)
179 std::stack
<int64
> dfs_sync_id_stack
;
180 // Push the topmost node.
181 dfs_sync_id_stack
.push(topmost_sync_id
);
182 while (!dfs_sync_id_stack
.empty()) {
183 const int64 sync_node_id
= dfs_sync_id_stack
.top();
184 syncer::WriteNode
node(trans
);
185 node
.InitByIdLookup(sync_node_id
);
186 if (!node
.GetIsFolder() || node
.GetFirstChildId() == syncer::kInvalidId
) {
187 // All children of the node has been processed, delete the node and
188 // pop it off the stack.
189 dfs_sync_id_stack
.pop();
190 // Do not delete the topmost node.
191 if (sync_node_id
!= topmost_sync_id
) {
192 RemoveOneSyncNode(&node
);
194 // if we are processing topmost node, all other nodes must be processed
195 // the stack should be empty.
196 DCHECK(dfs_sync_id_stack
.empty());
199 int64 child_id
= node
.GetFirstChildId();
200 if (child_id
!= syncer::kInvalidId
) {
201 dfs_sync_id_stack
.push(child_id
);
207 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel
* model
,
208 bool ids_reassigned
) {
212 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
213 BookmarkModel
* model
) {
215 bookmark_model_
= NULL
;
218 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel
* model
,
219 const BookmarkNode
* parent
,
221 DCHECK(share_handle());
223 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
224 int64 sync_id
= syncer::kInvalidId
;
226 // Acquire a scoped write lock via a transaction.
227 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
228 sync_id
= CreateSyncNode(parent
, model
, index
, &trans
,
229 model_associator_
, error_handler());
232 if (syncer::kInvalidId
!= sync_id
) {
233 // Siblings of added node in sync DB will also be updated to reflect new
234 // PREV_ID/NEXT_ID and thus get a new version. But we only update version
235 // of added node here. After switching to ordinals for positioning,
236 // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
237 UpdateTransactionVersion(
239 std::vector
<const BookmarkNode
*>(1, parent
->GetChild(index
)));
244 int64
BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode
* parent
,
245 BookmarkModel
* model
, int index
, syncer::WriteTransaction
* trans
,
246 BookmarkModelAssociator
* associator
,
247 DataTypeErrorHandler
* error_handler
) {
248 const BookmarkNode
* child
= parent
->GetChild(index
);
251 // Create a WriteNode container to hold the new node.
252 syncer::WriteNode
sync_child(trans
);
254 // Actually create the node with the appropriate initial position.
255 if (!PlaceSyncNode(CREATE
, parent
, index
, trans
, &sync_child
, associator
)) {
256 error_handler
->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
257 "Sync node creation failed; recovery unlikely");
258 return syncer::kInvalidId
;
261 UpdateSyncNodeProperties(child
, model
, &sync_child
);
263 // Associate the ID from the sync domain with the bookmark node, so that we
264 // can refer back to this item later.
265 associator
->Associate(child
, sync_child
.GetId());
267 return sync_child
.GetId();
270 void BookmarkChangeProcessor::BookmarkNodeRemoved(BookmarkModel
* model
,
271 const BookmarkNode
* parent
,
273 const BookmarkNode
* node
) {
274 RemoveSyncNodeHierarchy(node
);
277 void BookmarkChangeProcessor::BookmarkAllNodesRemoved(BookmarkModel
* model
) {
278 RemoveAllSyncNodes();
281 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel
* model
,
282 const BookmarkNode
* node
) {
283 // We shouldn't see changes to the top-level nodes.
284 if (model
->is_permanent_node(node
)) {
285 NOTREACHED() << "Saw update to permanent node!";
289 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
291 // Acquire a scoped write lock via a transaction.
292 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
294 // Lookup the sync node that's associated with |node|.
295 syncer::WriteNode
sync_node(&trans
);
296 if (!model_associator_
->InitSyncNodeFromChromeId(node
->id(), &sync_node
)) {
297 // TODO(tim): Investigating bug 121587.
298 if (model_associator_
->GetSyncIdFromChromeId(node
->id()) ==
299 syncer::kInvalidId
) {
300 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
301 "Bookmark id not found in model associator on BookmarkNodeChanged");
302 LOG(ERROR
) << "Bad id.";
303 } else if (!sync_node
.GetEntry()->good()) {
304 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
305 "Could not InitByIdLookup on BookmarkNodeChanged, good() failed");
306 LOG(ERROR
) << "Bad entry.";
307 } else if (sync_node
.GetEntry()->GetIsDel()) {
308 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
309 "Could not InitByIdLookup on BookmarkNodeChanged, is_del true");
310 LOG(ERROR
) << "Deleted entry.";
312 syncer::Cryptographer
* crypto
= trans
.GetCryptographer();
313 syncer::ModelTypeSet
encrypted_types(trans
.GetEncryptedTypes());
314 const sync_pb::EntitySpecifics
& specifics
=
315 sync_node
.GetEntry()->GetSpecifics();
316 CHECK(specifics
.has_encrypted());
317 const bool can_decrypt
= crypto
->CanDecrypt(specifics
.encrypted());
318 const bool agreement
= encrypted_types
.Has(syncer::BOOKMARKS
);
319 if (!agreement
&& !can_decrypt
) {
320 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
321 "Could not InitByIdLookup on BookmarkNodeChanged, "
322 " Cryptographer thinks bookmarks not encrypted, and CanDecrypt"
324 LOG(ERROR
) << "Case 1.";
325 } else if (agreement
&& can_decrypt
) {
326 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
327 "Could not InitByIdLookup on BookmarkNodeChanged, "
328 " Cryptographer thinks bookmarks are encrypted, and CanDecrypt"
329 " succeeded (?!), but DecryptIfNecessary failed.");
330 LOG(ERROR
) << "Case 2.";
331 } else if (agreement
) {
332 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
333 "Could not InitByIdLookup on BookmarkNodeChanged, "
334 " Cryptographer thinks bookmarks are encrypted, but CanDecrypt"
336 LOG(ERROR
) << "Case 3.";
338 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
339 "Could not InitByIdLookup on BookmarkNodeChanged, "
340 " Cryptographer thinks bookmarks not encrypted, but CanDecrypt"
341 " succeeded (super weird, btw)");
342 LOG(ERROR
) << "Case 4.";
348 UpdateSyncNodeProperties(node
, model
, &sync_node
);
350 DCHECK_EQ(sync_node
.GetIsFolder(), node
->is_folder());
351 DCHECK_EQ(model_associator_
->GetChromeNodeFromSyncId(
352 sync_node
.GetParentId()),
354 DCHECK_EQ(node
->parent()->GetIndexOf(node
), sync_node
.GetPositionIndex());
357 UpdateTransactionVersion(new_version
, model
,
358 std::vector
<const BookmarkNode
*>(1, node
));
361 void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
362 BookmarkModel
* model
, const BookmarkNode
* node
) {
363 BookmarkNodeChanged(model
, node
);
366 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel
* model
,
367 const BookmarkNode
* old_parent
, int old_index
,
368 const BookmarkNode
* new_parent
, int new_index
) {
369 const BookmarkNode
* child
= new_parent
->GetChild(new_index
);
370 // We shouldn't see changes to the top-level nodes.
371 if (model
->is_permanent_node(child
)) {
372 NOTREACHED() << "Saw update to permanent node!";
376 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
378 // Acquire a scoped write lock via a transaction.
379 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
381 // Lookup the sync node that's associated with |child|.
382 syncer::WriteNode
sync_node(&trans
);
383 if (!model_associator_
->InitSyncNodeFromChromeId(child
->id(), &sync_node
)) {
384 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
389 if (!PlaceSyncNode(MOVE
, new_parent
, new_index
, &trans
, &sync_node
,
390 model_associator_
)) {
391 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
397 UpdateTransactionVersion(new_version
, model
,
398 std::vector
<const BookmarkNode
*>(1, child
));
401 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
402 BookmarkModel
* model
,
403 const BookmarkNode
* node
) {
404 BookmarkNodeChanged(model
, node
);
407 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
408 BookmarkModel
* model
, const BookmarkNode
* node
) {
409 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
410 std::vector
<const BookmarkNode
*> children
;
412 // Acquire a scoped write lock via a transaction.
413 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
415 // The given node's children got reordered. We need to reorder all the
416 // children of the corresponding sync node.
417 for (int i
= 0; i
< node
->child_count(); ++i
) {
418 const BookmarkNode
* child
= node
->GetChild(i
);
419 children
.push_back(child
);
421 syncer::WriteNode
sync_child(&trans
);
422 if (!model_associator_
->InitSyncNodeFromChromeId(child
->id(),
424 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
428 DCHECK_EQ(sync_child
.GetParentId(),
429 model_associator_
->GetSyncIdFromChromeId(node
->id()));
431 if (!PlaceSyncNode(MOVE
, node
, i
, &trans
, &sync_child
,
432 model_associator_
)) {
433 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
440 // TODO(haitaol): Filter out children that didn't actually change.
441 UpdateTransactionVersion(new_version
, model
, children
);
445 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation
,
446 const BookmarkNode
* parent
, int index
, syncer::WriteTransaction
* trans
,
447 syncer::WriteNode
* dst
, BookmarkModelAssociator
* associator
) {
448 syncer::ReadNode
sync_parent(trans
);
449 if (!associator
->InitSyncNodeFromChromeId(parent
->id(), &sync_parent
)) {
450 LOG(WARNING
) << "Parent lookup failed";
454 bool success
= false;
456 // Insert into first position.
457 success
= (operation
== CREATE
) ?
458 dst
->InitBookmarkByCreation(sync_parent
, NULL
) :
459 dst
->SetPosition(sync_parent
, NULL
);
461 DCHECK_EQ(dst
->GetParentId(), sync_parent
.GetId());
462 DCHECK_EQ(dst
->GetId(), sync_parent
.GetFirstChildId());
463 DCHECK_EQ(dst
->GetPredecessorId(), syncer::kInvalidId
);
466 // Find the bookmark model predecessor, and insert after it.
467 const BookmarkNode
* prev
= parent
->GetChild(index
- 1);
468 syncer::ReadNode
sync_prev(trans
);
469 if (!associator
->InitSyncNodeFromChromeId(prev
->id(), &sync_prev
)) {
470 LOG(WARNING
) << "Predecessor lookup failed";
473 success
= (operation
== CREATE
) ?
474 dst
->InitBookmarkByCreation(sync_parent
, &sync_prev
) :
475 dst
->SetPosition(sync_parent
, &sync_prev
);
477 DCHECK_EQ(dst
->GetParentId(), sync_parent
.GetId());
478 DCHECK_EQ(dst
->GetPredecessorId(), sync_prev
.GetId());
479 DCHECK_EQ(dst
->GetId(), sync_prev
.GetSuccessorId());
485 // ApplyModelChanges is called by the sync backend after changes have been made
486 // to the sync engine's model. Apply these changes to the browser bookmark
488 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
489 const syncer::BaseTransaction
* trans
,
491 const syncer::ImmutableChangeRecordList
& changes
) {
492 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
493 // A note about ordering. Sync backend is responsible for ordering the change
494 // records in the following order:
496 // 1. Deletions, from leaves up to parents.
497 // 2. Existing items with synced parents & predecessors.
498 // 3. New items with synced parents & predecessors.
499 // 4. Items with parents & predecessors in the list.
500 // 5. Repeat #4 until all items are in the list.
502 // "Predecessor" here means the previous item within a given folder; an item
503 // in the first position is always said to have a synced predecessor.
504 // For the most part, applying these changes in the order given will yield
505 // the correct result. There is one exception, however: for items that are
506 // moved away from a folder that is being deleted, we will process the delete
507 // before the move. Since deletions in the bookmark model propagate from
508 // parent to child, we must move them to a temporary location.
509 BookmarkModel
* model
= bookmark_model_
;
511 // We are going to make changes to the bookmarks model, but don't want to end
512 // up in a feedback loop, so remove ourselves as an observer while applying
514 model
->RemoveObserver(this);
516 // Changes made to the bookmark model due to sync should not be undoable.
517 #if !defined(OS_ANDROID)
518 ScopedSuspendUndoTracking
suspend_undo(
519 BookmarkUndoServiceFactory::GetForProfile(profile_
)->undo_manager());
522 // Notify UI intensive observers of BookmarkModel that we are about to make
523 // potentially significant changes to it, so the updates may be batched. For
524 // example, on Mac, the bookmarks bar displays animations when bookmark items
525 // are added or deleted.
526 model
->BeginExtensiveChanges();
528 // A parent to hold nodes temporarily orphaned by parent deletion. It is
529 // created only if it is needed.
530 const BookmarkNode
* foster_parent
= NULL
;
532 // Iterate over the deletions, which are always at the front of the list.
533 ChangeRecordList::const_iterator it
;
534 for (it
= changes
.Get().begin();
535 it
!= changes
.Get().end() && it
->action
== ChangeRecord::ACTION_DELETE
;
537 const BookmarkNode
* dst
=
538 model_associator_
->GetChromeNodeFromSyncId(it
->id
);
540 // Ignore changes to the permanent top-level nodes. We only care about
542 if (model
->is_permanent_node(dst
))
545 // Can't do anything if we can't find the chrome node.
549 // Children of a deleted node should not be deleted; they may be
550 // reparented by a later change record. Move them to a temporary place.
552 if (!foster_parent
) {
553 foster_parent
= model
->AddFolder(model
->other_node(),
554 model
->other_node()->child_count(),
556 if (!foster_parent
) {
557 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
558 "Failed to create foster parent.");
562 for (int i
= dst
->child_count() - 1; i
>= 0; --i
) {
563 model
->Move(dst
->GetChild(i
), foster_parent
,
564 foster_parent
->child_count());
567 DCHECK_EQ(dst
->child_count(), 0) << "Node being deleted has children";
569 model_associator_
->Disassociate(it
->id
);
571 const BookmarkNode
* parent
= dst
->parent();
572 int index
= parent
->GetIndexOf(dst
);
574 model
->Remove(parent
, index
);
577 // A map to keep track of some reordering work we defer until later.
578 std::multimap
<int, const BookmarkNode
*> to_reposition
;
580 syncer::ReadNode
synced_bookmarks(trans
);
581 int64 synced_bookmarks_id
= syncer::kInvalidId
;
582 if (synced_bookmarks
.InitByTagLookup(kMobileBookmarksTag
) ==
583 syncer::BaseNode::INIT_OK
) {
584 synced_bookmarks_id
= synced_bookmarks
.GetId();
587 // Continue iterating where the previous loop left off.
588 for ( ; it
!= changes
.Get().end(); ++it
) {
589 const BookmarkNode
* dst
=
590 model_associator_
->GetChromeNodeFromSyncId(it
->id
);
592 // Ignore changes to the permanent top-level nodes. We only care about
594 if (model
->is_permanent_node(dst
))
597 // Because the Synced Bookmarks node can be created server side, it's
598 // possible it'll arrive at the client as an update. In that case it won't
599 // have been associated at startup, the GetChromeNodeFromSyncId call above
600 // will return NULL, and we won't detect it as a permanent node, resulting
601 // in us trying to create it here (which will fail). Therefore, we add
602 // special logic here just to detect the Synced Bookmarks folder.
603 if (synced_bookmarks_id
!= syncer::kInvalidId
&&
604 it
->id
== synced_bookmarks_id
) {
605 // This is a newly created Synced Bookmarks node. Associate it.
606 model_associator_
->Associate(model
->mobile_node(), it
->id
);
610 DCHECK_NE(it
->action
, ChangeRecord::ACTION_DELETE
)
611 << "We should have passed all deletes by this point.";
613 syncer::ReadNode
src(trans
);
614 if (src
.InitByIdLookup(it
->id
) != syncer::BaseNode::INIT_OK
) {
615 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
616 "ApplyModelChanges was passed a bad ID");
620 const BookmarkNode
* parent
=
621 model_associator_
->GetChromeNodeFromSyncId(src
.GetParentId());
623 LOG(ERROR
) << "Could not find parent of node being added/updated."
624 << " Node title: " << src
.GetTitle()
625 << ", parent id = " << src
.GetParentId();
630 DCHECK(it
->action
== ChangeRecord::ACTION_UPDATE
)
631 << "ACTION_UPDATE should be seen if and only if the node is known.";
632 UpdateBookmarkWithSyncData(src
, model
, dst
, profile_
);
634 // Move all modified entries to the right. We'll fix it later.
635 model
->Move(dst
, parent
, parent
->child_count());
637 DCHECK(it
->action
== ChangeRecord::ACTION_ADD
)
638 << "ACTION_ADD should be seen if and only if the node is unknown.";
640 dst
= CreateBookmarkNode(&src
,
644 parent
->child_count());
646 // We ignore bookmarks we can't add. Chances are this is caused by
647 // a bookmark that was not fully associated.
648 LOG(ERROR
) << "Failed to create bookmark node with title "
649 << src
.GetTitle() + " and url "
650 << src
.GetBookmarkSpecifics().url();
653 model_associator_
->Associate(dst
, src
.GetId());
656 to_reposition
.insert(std::make_pair(src
.GetPositionIndex(), dst
));
657 bookmark_model_
->SetNodeSyncTransactionVersion(dst
, model_version
);
660 // When we added or updated bookmarks in the previous loop, we placed them to
661 // the far right position. Now we iterate over all these modified items in
662 // sync order, left to right, moving them into their proper positions.
663 for (std::multimap
<int, const BookmarkNode
*>::iterator it
=
664 to_reposition
.begin(); it
!= to_reposition
.end(); ++it
) {
665 const BookmarkNode
* parent
= it
->second
->parent();
666 model
->Move(it
->second
, parent
, it
->first
);
669 // Clean up the temporary node.
671 // There should be no nodes left under the foster parent.
672 DCHECK_EQ(foster_parent
->child_count(), 0);
673 model
->Remove(foster_parent
->parent(),
674 foster_parent
->parent()->GetIndexOf(foster_parent
));
675 foster_parent
= NULL
;
678 // The visibility of the mobile node may need to change.
679 model_associator_
->UpdatePermanentNodeVisibility();
681 // Notify UI intensive observers of BookmarkModel that all updates have been
682 // applied, and that they may now be consumed. This prevents issues like the
683 // one described in crbug.com/281562, where old and new items on the bookmarks
684 // bar would overlap.
685 model
->EndExtensiveChanges();
687 // We are now ready to hear about bookmarks changes again.
688 model
->AddObserver(this);
690 // All changes are applied in bookmark model. Set transaction version on
691 // bookmark model to mark as synced.
692 model
->SetNodeSyncTransactionVersion(model
->root_node(), model_version
);
696 // Update a bookmark node with specified sync data.
697 void BookmarkChangeProcessor::UpdateBookmarkWithSyncData(
698 const syncer::BaseNode
& sync_node
,
699 BookmarkModel
* model
,
700 const BookmarkNode
* node
,
702 DCHECK_EQ(sync_node
.GetIsFolder(), node
->is_folder());
703 const sync_pb::BookmarkSpecifics
& specifics
=
704 sync_node
.GetBookmarkSpecifics();
705 if (!sync_node
.GetIsFolder())
706 model
->SetURL(node
, GURL(specifics
.url()));
707 model
->SetTitle(node
, base::UTF8ToUTF16(sync_node
.GetTitle()));
708 if (specifics
.has_creation_time_us()) {
711 base::Time::FromInternalValue(specifics
.creation_time_us()));
713 SetBookmarkFavicon(&sync_node
, node
, model
, profile
);
714 SetBookmarkMetaInfo(&sync_node
, node
, model
);
718 void BookmarkChangeProcessor::UpdateTransactionVersion(
720 BookmarkModel
* model
,
721 const std::vector
<const BookmarkNode
*>& nodes
) {
722 if (new_version
!= syncer::syncable::kInvalidTransactionVersion
) {
723 model
->SetNodeSyncTransactionVersion(model
->root_node(), new_version
);
724 for (size_t i
= 0; i
< nodes
.size(); ++i
) {
725 model
->SetNodeSyncTransactionVersion(nodes
[i
], new_version
);
731 // Creates a bookmark node under the given parent node from the given sync
732 // node. Returns the newly created node.
733 const BookmarkNode
* BookmarkChangeProcessor::CreateBookmarkNode(
734 syncer::BaseNode
* sync_node
,
735 const BookmarkNode
* parent
,
736 BookmarkModel
* model
,
741 const BookmarkNode
* node
;
742 if (sync_node
->GetIsFolder()) {
743 node
= model
->AddFolder(
744 parent
, index
, base::UTF8ToUTF16(sync_node
->GetTitle()));
746 // 'creation_time_us' was added in m24. Assume a time of 0 means now.
747 const sync_pb::BookmarkSpecifics
& specifics
=
748 sync_node
->GetBookmarkSpecifics();
749 const int64 create_time_internal
= specifics
.creation_time_us();
750 base::Time create_time
= (create_time_internal
== 0) ?
751 base::Time::Now() : base::Time::FromInternalValue(create_time_internal
);
752 node
= model
->AddURLWithCreationTime(parent
, index
,
754 sync_node
->GetTitle()),
755 GURL(specifics
.url()), create_time
);
757 SetBookmarkFavicon(sync_node
, node
, model
, profile
);
760 SetBookmarkMetaInfo(sync_node
, node
, model
);
765 // Sets the favicon of the given bookmark node from the given sync node.
766 bool BookmarkChangeProcessor::SetBookmarkFavicon(
767 const syncer::BaseNode
* sync_node
,
768 const BookmarkNode
* bookmark_node
,
769 BookmarkModel
* bookmark_model
,
771 const sync_pb::BookmarkSpecifics
& specifics
=
772 sync_node
->GetBookmarkSpecifics();
773 const std::string
& icon_bytes_str
= specifics
.favicon();
774 if (icon_bytes_str
.empty())
777 scoped_refptr
<base::RefCountedString
> icon_bytes(
778 new base::RefCountedString());
779 icon_bytes
->data().assign(icon_bytes_str
);
780 GURL
icon_url(specifics
.icon_url());
782 // Old clients may not be syncing the favicon URL. If the icon URL is not
783 // synced, use the page URL as a fake icon URL as it is guaranteed to be
785 if (icon_url
.is_empty())
786 icon_url
= bookmark_node
->url();
788 ApplyBookmarkFavicon(bookmark_node
, profile
, icon_url
, icon_bytes
);
794 void BookmarkChangeProcessor::SetBookmarkMetaInfo(
795 const syncer::BaseNode
* sync_node
,
796 const BookmarkNode
* bookmark_node
,
797 BookmarkModel
* bookmark_model
) {
798 const sync_pb::BookmarkSpecifics
& specifics
=
799 sync_node
->GetBookmarkSpecifics();
800 BookmarkNode::MetaInfoMap meta_info_map
;
801 for (int i
= 0; i
< specifics
.meta_info_size(); ++i
) {
802 meta_info_map
[specifics
.meta_info(i
).key()] =
803 specifics
.meta_info(i
).value();
805 bookmark_model
->SetNodeMetaInfoMap(bookmark_node
, meta_info_map
);
809 void BookmarkChangeProcessor::SetSyncNodeMetaInfo(
810 const BookmarkNode
* node
,
811 syncer::WriteNode
* sync_node
) {
812 sync_pb::BookmarkSpecifics specifics
= sync_node
->GetBookmarkSpecifics();
813 specifics
.clear_meta_info();
814 const BookmarkNode::MetaInfoMap
* meta_info_map
= node
->GetMetaInfoMap();
816 for (BookmarkNode::MetaInfoMap::const_iterator it
= meta_info_map
->begin();
817 it
!= meta_info_map
->end(); ++it
) {
818 sync_pb::MetaInfo
* meta_info
= specifics
.add_meta_info();
819 meta_info
->set_key(it
->first
);
820 meta_info
->set_value(it
->second
);
823 sync_node
->SetBookmarkSpecifics(specifics
);
827 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
828 const BookmarkNode
* bookmark_node
,
830 const GURL
& icon_url
,
831 const scoped_refptr
<base::RefCountedMemory
>& bitmap_data
) {
832 HistoryService
* history
=
833 HistoryServiceFactory::GetForProfile(profile
, Profile::EXPLICIT_ACCESS
);
834 FaviconService
* favicon_service
=
835 FaviconServiceFactory::GetForProfile(profile
, Profile::EXPLICIT_ACCESS
);
837 history
->AddPageNoVisitForBookmark(bookmark_node
->url(),
838 bookmark_node
->GetTitle());
839 // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
840 // overwrite the cached 2x favicon bitmap. Sync favicons are always
841 // gfx::kFaviconSize in width and height. Store the favicon into history
843 gfx::Size
pixel_size(gfx::kFaviconSize
, gfx::kFaviconSize
);
844 favicon_service
->MergeFavicon(bookmark_node
->url(),
852 void BookmarkChangeProcessor::SetSyncNodeFavicon(
853 const BookmarkNode
* bookmark_node
,
854 BookmarkModel
* model
,
855 syncer::WriteNode
* sync_node
) {
856 scoped_refptr
<base::RefCountedMemory
> favicon_bytes(NULL
);
857 EncodeFavicon(bookmark_node
, model
, &favicon_bytes
);
858 if (favicon_bytes
.get() && favicon_bytes
->size()) {
859 sync_pb::BookmarkSpecifics
updated_specifics(
860 sync_node
->GetBookmarkSpecifics());
861 updated_specifics
.set_favicon(favicon_bytes
->front(),
862 favicon_bytes
->size());
863 updated_specifics
.set_icon_url(bookmark_node
->icon_url().spec());
864 sync_node
->SetBookmarkSpecifics(updated_specifics
);
868 } // namespace browser_sync