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_factory.h"
17 #include "chrome/browser/favicon/favicon_service_factory.h"
18 #include "chrome/browser/history/history_service_factory.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sync/profile_sync_service.h"
21 #include "chrome/browser/undo/bookmark_undo_service.h"
22 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
23 #include "chrome/browser/undo/bookmark_undo_utils.h"
24 #include "components/bookmarks/browser/bookmark_client.h"
25 #include "components/bookmarks/browser/bookmark_model.h"
26 #include "components/bookmarks/browser/bookmark_utils.h"
27 #include "components/favicon/core/favicon_service.h"
28 #include "components/history/core/browser/history_service.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "sync/internal_api/public/change_record.h"
31 #include "sync/internal_api/public/read_node.h"
32 #include "sync/internal_api/public/write_node.h"
33 #include "sync/internal_api/public/write_transaction.h"
34 #include "sync/syncable/entry.h" // TODO(tim): Investigating bug 121587.
35 #include "sync/syncable/syncable_write_transaction.h"
36 #include "ui/gfx/favicon_size.h"
37 #include "ui/gfx/image/image_util.h"
39 using bookmarks::BookmarkModel
;
40 using bookmarks::BookmarkNode
;
41 using content::BrowserThread
;
42 using syncer::ChangeRecord
;
43 using syncer::ChangeRecordList
;
45 namespace browser_sync
{
47 static const char kMobileBookmarksTag
[] = "synced_bookmarks";
49 BookmarkChangeProcessor::BookmarkChangeProcessor(
51 BookmarkModelAssociator
* model_associator
,
52 sync_driver::DataTypeErrorHandler
* error_handler
)
53 : sync_driver::ChangeProcessor(error_handler
),
54 bookmark_model_(NULL
),
56 model_associator_(model_associator
) {
57 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
58 DCHECK(model_associator
);
60 DCHECK(error_handler
);
63 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
65 bookmark_model_
->RemoveObserver(this);
68 void BookmarkChangeProcessor::StartImpl() {
69 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
70 DCHECK(!bookmark_model_
);
71 bookmark_model_
= BookmarkModelFactory::GetForProfile(profile_
);
72 DCHECK(bookmark_model_
->loaded());
73 bookmark_model_
->AddObserver(this);
76 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
77 const BookmarkNode
* src
,
79 syncer::WriteNode
* dst
) {
80 // Set the properties of the item.
81 dst
->SetIsFolder(src
->is_folder());
82 dst
->SetTitle(base::UTF16ToUTF8(src
->GetTitle()));
83 sync_pb::BookmarkSpecifics
bookmark_specifics(dst
->GetBookmarkSpecifics());
84 if (!src
->is_folder())
85 bookmark_specifics
.set_url(src
->url().spec());
86 bookmark_specifics
.set_creation_time_us(src
->date_added().ToInternalValue());
87 dst
->SetBookmarkSpecifics(bookmark_specifics
);
88 SetSyncNodeFavicon(src
, model
, dst
);
89 SetSyncNodeMetaInfo(src
, dst
);
93 void BookmarkChangeProcessor::EncodeFavicon(
94 const BookmarkNode
* src
,
96 scoped_refptr
<base::RefCountedMemory
>* dst
) {
97 const gfx::Image
& favicon
= model
->GetFavicon(src
);
99 // Check for empty images. This can happen if the favicon is
100 // still being loaded.
101 if (favicon
.IsEmpty())
104 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
106 *dst
= favicon
.As1xPNGBytes();
109 void BookmarkChangeProcessor::RemoveOneSyncNode(syncer::WriteNode
* sync_node
) {
110 // This node should have no children.
111 DCHECK(!sync_node
->HasChildren());
112 // Remove association and delete the sync node.
113 model_associator_
->Disassociate(sync_node
->GetId());
114 sync_node
->Tombstone();
117 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
118 const BookmarkNode
* topmost
) {
120 syncer::syncable::kInvalidTransactionVersion
;
122 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
123 syncer::WriteNode
topmost_sync_node(&trans
);
124 if (!model_associator_
->InitSyncNodeFromChromeId(topmost
->id(),
125 &topmost_sync_node
)) {
126 syncer::SyncError
error(FROM_HERE
,
127 syncer::SyncError::DATATYPE_ERROR
,
128 "Failed to init sync node from chrome node",
130 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
133 // Check that |topmost| has been unlinked.
134 DCHECK(topmost
->is_root());
135 RemoveAllChildNodes(&trans
, topmost
->id());
136 // Remove the node itself.
137 RemoveOneSyncNode(&topmost_sync_node
);
140 // Don't need to update versions of deleted nodes.
141 UpdateTransactionVersion(new_version
, bookmark_model_
,
142 std::vector
<const BookmarkNode
*>());
145 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
146 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
148 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
150 RemoveAllChildNodes(&trans
, bookmark_model_
->bookmark_bar_node()->id());
151 RemoveAllChildNodes(&trans
, bookmark_model_
->other_node()->id());
152 // Remove mobile bookmarks node only if it is present.
153 const int64 mobile_bookmark_id
= bookmark_model_
->mobile_node()->id();
154 if (model_associator_
->GetSyncIdFromChromeId(mobile_bookmark_id
) !=
155 syncer::kInvalidId
) {
156 RemoveAllChildNodes(&trans
, bookmark_model_
->mobile_node()->id());
158 // Note: the root node may have additional extra nodes. Currently none of
159 // them are meant to sync.
162 // Don't need to update versions of deleted nodes.
163 UpdateTransactionVersion(new_version
, bookmark_model_
,
164 std::vector
<const BookmarkNode
*>());
167 void BookmarkChangeProcessor::RemoveAllChildNodes(
168 syncer::WriteTransaction
* trans
, const int64
& topmost_node_id
) {
169 syncer::WriteNode
topmost_node(trans
);
170 if (!model_associator_
->InitSyncNodeFromChromeId(topmost_node_id
,
172 syncer::SyncError
error(FROM_HERE
,
173 syncer::SyncError::DATATYPE_ERROR
,
174 "Failed to init sync node from chrome node",
176 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
179 const int64 topmost_sync_id
= topmost_node
.GetId();
181 // Do a DFS and delete all the child sync nodes, use sync id instead of
182 // bookmark node ids since the bookmark nodes may already be deleted.
183 // The equivalent recursive version of this iterative DFS:
184 // remove_all_children(node_id, topmost_node_id):
185 // node.initByIdLookup(node_id)
186 // while(node.GetFirstChildId() != syncer::kInvalidId)
187 // remove_all_children(node.GetFirstChildId(), topmost_node_id)
188 // if(node_id != topmost_node_id)
191 std::stack
<int64
> dfs_sync_id_stack
;
192 // Push the topmost node.
193 dfs_sync_id_stack
.push(topmost_sync_id
);
194 while (!dfs_sync_id_stack
.empty()) {
195 const int64 sync_node_id
= dfs_sync_id_stack
.top();
196 syncer::WriteNode
node(trans
);
197 node
.InitByIdLookup(sync_node_id
);
198 if (!node
.GetIsFolder() || node
.GetFirstChildId() == syncer::kInvalidId
) {
199 // All children of the node has been processed, delete the node and
200 // pop it off the stack.
201 dfs_sync_id_stack
.pop();
202 // Do not delete the topmost node.
203 if (sync_node_id
!= topmost_sync_id
) {
204 RemoveOneSyncNode(&node
);
206 // if we are processing topmost node, all other nodes must be processed
207 // the stack should be empty.
208 DCHECK(dfs_sync_id_stack
.empty());
211 int64 child_id
= node
.GetFirstChildId();
212 if (child_id
!= syncer::kInvalidId
) {
213 dfs_sync_id_stack
.push(child_id
);
219 void BookmarkChangeProcessor::CreateOrUpdateSyncNode(const BookmarkNode
* node
) {
220 if (!CanSyncNode(node
)) {
225 const BookmarkNode
* parent
= node
->parent();
226 int index
= node
->parent()->GetIndexOf(node
);
228 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
229 int64 sync_id
= syncer::kInvalidId
;
231 // Acquire a scoped write lock via a transaction.
232 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
233 sync_id
= model_associator_
->GetSyncIdFromChromeId(node
->id());
234 if (sync_id
!= syncer::kInvalidId
) {
236 node
, bookmark_model_
, &trans
, model_associator_
, error_handler());
238 sync_id
= CreateSyncNode(parent
,
247 if (syncer::kInvalidId
!= sync_id
) {
248 // Siblings of added node in sync DB will also be updated to reflect new
249 // PREV_ID/NEXT_ID and thus get a new version. But we only update version
250 // of added node here. After switching to ordinals for positioning,
251 // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
252 UpdateTransactionVersion(
255 std::vector
<const BookmarkNode
*>(1, parent
->GetChild(index
)));
259 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel
* model
,
260 bool ids_reassigned
) {
264 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel
* model
) {
266 bookmark_model_
= NULL
;
269 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel
* model
,
270 const BookmarkNode
* parent
,
272 DCHECK(share_handle());
273 const BookmarkNode
* node
= parent
->GetChild(index
);
274 if (CanSyncNode(node
))
275 CreateOrUpdateSyncNode(node
);
279 int64
BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode
* parent
,
280 BookmarkModel
* model
, int index
, syncer::WriteTransaction
* trans
,
281 BookmarkModelAssociator
* associator
,
282 sync_driver::DataTypeErrorHandler
* error_handler
) {
283 const BookmarkNode
* child
= parent
->GetChild(index
);
286 // Create a WriteNode container to hold the new node.
287 syncer::WriteNode
sync_child(trans
);
289 // Actually create the node with the appropriate initial position.
290 if (!PlaceSyncNode(CREATE
, parent
, index
, trans
, &sync_child
, associator
)) {
291 syncer::SyncError
error(FROM_HERE
,
292 syncer::SyncError::DATATYPE_ERROR
,
293 "Failed ot creat sync node.",
295 error_handler
->OnSingleDataTypeUnrecoverableError(error
);
296 return syncer::kInvalidId
;
299 UpdateSyncNodeProperties(child
, model
, &sync_child
);
301 // Associate the ID from the sync domain with the bookmark node, so that we
302 // can refer back to this item later.
303 associator
->Associate(child
, sync_child
.GetId());
305 return sync_child
.GetId();
308 void BookmarkChangeProcessor::BookmarkNodeRemoved(
309 BookmarkModel
* model
,
310 const BookmarkNode
* parent
,
312 const BookmarkNode
* node
,
313 const std::set
<GURL
>& removed_urls
) {
314 if (CanSyncNode(node
))
315 RemoveSyncNodeHierarchy(node
);
318 void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved(
319 BookmarkModel
* model
,
320 const std::set
<GURL
>& removed_urls
) {
321 RemoveAllSyncNodes();
324 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel
* model
,
325 const BookmarkNode
* node
) {
326 if (!CanSyncNode(node
))
328 // We shouldn't see changes to the top-level nodes.
329 if (model
->is_permanent_node(node
)) {
330 NOTREACHED() << "Saw update to permanent node!";
333 CreateOrUpdateSyncNode(node
);
337 int64
BookmarkChangeProcessor::UpdateSyncNode(
338 const BookmarkNode
* node
,
339 BookmarkModel
* model
,
340 syncer::WriteTransaction
* trans
,
341 BookmarkModelAssociator
* associator
,
342 sync_driver::DataTypeErrorHandler
* error_handler
) {
343 // Lookup the sync node that's associated with |node|.
344 syncer::WriteNode
sync_node(trans
);
345 if (!associator
->InitSyncNodeFromChromeId(node
->id(), &sync_node
)) {
346 syncer::SyncError
error(FROM_HERE
,
347 syncer::SyncError::DATATYPE_ERROR
,
348 "Failed to init sync node from chrome node",
350 error_handler
->OnSingleDataTypeUnrecoverableError(error
);
351 return syncer::kInvalidId
;
353 UpdateSyncNodeProperties(node
, model
, &sync_node
);
354 DCHECK_EQ(sync_node
.GetIsFolder(), node
->is_folder());
355 DCHECK_EQ(associator
->GetChromeNodeFromSyncId(sync_node
.GetParentId()),
357 DCHECK_EQ(node
->parent()->GetIndexOf(node
), sync_node
.GetPositionIndex());
358 return sync_node
.GetId();
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
);
371 if (!CanSyncNode(child
))
374 // We shouldn't see changes to the top-level nodes.
375 if (model
->is_permanent_node(child
)) {
376 NOTREACHED() << "Saw update to permanent node!";
380 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
382 // Acquire a scoped write lock via a transaction.
383 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
385 // Lookup the sync node that's associated with |child|.
386 syncer::WriteNode
sync_node(&trans
);
387 if (!model_associator_
->InitSyncNodeFromChromeId(child
->id(), &sync_node
)) {
388 syncer::SyncError
error(FROM_HERE
,
389 syncer::SyncError::DATATYPE_ERROR
,
390 "Failed to init sync node from chrome node",
392 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
396 if (!PlaceSyncNode(MOVE
, new_parent
, new_index
, &trans
, &sync_node
,
397 model_associator_
)) {
398 syncer::SyncError
error(FROM_HERE
,
399 syncer::SyncError::DATATYPE_ERROR
,
400 "Failed to place sync node",
402 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
407 UpdateTransactionVersion(new_version
, model
,
408 std::vector
<const BookmarkNode
*>(1, child
));
411 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
412 BookmarkModel
* model
,
413 const BookmarkNode
* node
) {
414 if (!CanSyncNode(node
)) {
418 // We shouldn't see changes to the top-level nodes.
419 if (model
->is_permanent_node(node
)) {
420 NOTREACHED() << "Saw Favicon update to permanent node!";
424 // Ignore changes with empty images. This can happen if the favicon is
425 // still being loaded.
426 const gfx::Image
& favicon
= model
->GetFavicon(node
);
427 if (favicon
.IsEmpty()) {
431 BookmarkNodeChanged(model
, node
);
434 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
435 BookmarkModel
* model
, const BookmarkNode
* node
) {
436 if (!CanSyncNode(node
))
438 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
439 std::vector
<const BookmarkNode
*> children
;
441 // Acquire a scoped write lock via a transaction.
442 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
444 // The given node's children got reordered. We need to reorder all the
445 // children of the corresponding sync node.
446 for (int i
= 0; i
< node
->child_count(); ++i
) {
447 const BookmarkNode
* child
= node
->GetChild(i
);
448 children
.push_back(child
);
450 syncer::WriteNode
sync_child(&trans
);
451 if (!model_associator_
->InitSyncNodeFromChromeId(child
->id(),
453 syncer::SyncError
error(FROM_HERE
,
454 syncer::SyncError::DATATYPE_ERROR
,
455 "Failed to init sync node from chrome node",
457 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
460 DCHECK_EQ(sync_child
.GetParentId(),
461 model_associator_
->GetSyncIdFromChromeId(node
->id()));
463 if (!PlaceSyncNode(MOVE
, node
, i
, &trans
, &sync_child
,
464 model_associator_
)) {
465 syncer::SyncError
error(FROM_HERE
,
466 syncer::SyncError::DATATYPE_ERROR
,
467 "Failed to place sync node",
469 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
475 // TODO(haitaol): Filter out children that didn't actually change.
476 UpdateTransactionVersion(new_version
, model
, children
);
480 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation
,
481 const BookmarkNode
* parent
, int index
, syncer::WriteTransaction
* trans
,
482 syncer::WriteNode
* dst
, BookmarkModelAssociator
* associator
) {
483 syncer::ReadNode
sync_parent(trans
);
484 if (!associator
->InitSyncNodeFromChromeId(parent
->id(), &sync_parent
)) {
485 LOG(WARNING
) << "Parent lookup failed";
489 bool success
= false;
491 // Insert into first position.
492 success
= (operation
== CREATE
) ?
493 dst
->InitBookmarkByCreation(sync_parent
, NULL
) :
494 dst
->SetPosition(sync_parent
, NULL
);
496 DCHECK_EQ(dst
->GetParentId(), sync_parent
.GetId());
497 DCHECK_EQ(dst
->GetId(), sync_parent
.GetFirstChildId());
498 DCHECK_EQ(dst
->GetPredecessorId(), syncer::kInvalidId
);
501 // Find the bookmark model predecessor, and insert after it.
502 const BookmarkNode
* prev
= parent
->GetChild(index
- 1);
503 syncer::ReadNode
sync_prev(trans
);
504 if (!associator
->InitSyncNodeFromChromeId(prev
->id(), &sync_prev
)) {
505 LOG(WARNING
) << "Predecessor lookup failed";
508 success
= (operation
== CREATE
) ?
509 dst
->InitBookmarkByCreation(sync_parent
, &sync_prev
) :
510 dst
->SetPosition(sync_parent
, &sync_prev
);
512 DCHECK_EQ(dst
->GetParentId(), sync_parent
.GetId());
513 DCHECK_EQ(dst
->GetPredecessorId(), sync_prev
.GetId());
514 DCHECK_EQ(dst
->GetId(), sync_prev
.GetSuccessorId());
520 // ApplyModelChanges is called by the sync backend after changes have been made
521 // to the sync engine's model. Apply these changes to the browser bookmark
523 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
524 const syncer::BaseTransaction
* trans
,
526 const syncer::ImmutableChangeRecordList
& changes
) {
527 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
528 // A note about ordering. Sync backend is responsible for ordering the change
529 // records in the following order:
531 // 1. Deletions, from leaves up to parents.
532 // 2. Existing items with synced parents & predecessors.
533 // 3. New items with synced parents & predecessors.
534 // 4. Items with parents & predecessors in the list.
535 // 5. Repeat #4 until all items are in the list.
537 // "Predecessor" here means the previous item within a given folder; an item
538 // in the first position is always said to have a synced predecessor.
539 // For the most part, applying these changes in the order given will yield
540 // the correct result. There is one exception, however: for items that are
541 // moved away from a folder that is being deleted, we will process the delete
542 // before the move. Since deletions in the bookmark model propagate from
543 // parent to child, we must move them to a temporary location.
544 BookmarkModel
* model
= bookmark_model_
;
546 // We are going to make changes to the bookmarks model, but don't want to end
547 // up in a feedback loop, so remove ourselves as an observer while applying
549 model
->RemoveObserver(this);
551 // Changes made to the bookmark model due to sync should not be undoable.
552 ScopedSuspendBookmarkUndo
suspend_undo(profile_
);
554 // Notify UI intensive observers of BookmarkModel that we are about to make
555 // potentially significant changes to it, so the updates may be batched. For
556 // example, on Mac, the bookmarks bar displays animations when bookmark items
557 // are added or deleted.
558 model
->BeginExtensiveChanges();
560 // A parent to hold nodes temporarily orphaned by parent deletion. It is
561 // created only if it is needed.
562 const BookmarkNode
* foster_parent
= NULL
;
564 // Iterate over the deletions, which are always at the front of the list.
565 ChangeRecordList::const_iterator it
;
566 for (it
= changes
.Get().begin();
567 it
!= changes
.Get().end() && it
->action
== ChangeRecord::ACTION_DELETE
;
569 const BookmarkNode
* dst
=
570 model_associator_
->GetChromeNodeFromSyncId(it
->id
);
572 // Ignore changes to the permanent top-level nodes. We only care about
574 if (model
->is_permanent_node(dst
))
577 // Can't do anything if we can't find the chrome node.
581 // Children of a deleted node should not be deleted; they may be
582 // reparented by a later change record. Move them to a temporary place.
584 if (!foster_parent
) {
585 foster_parent
= model
->AddFolder(model
->other_node(),
586 model
->other_node()->child_count(),
588 if (!foster_parent
) {
589 syncer::SyncError
error(FROM_HERE
,
590 syncer::SyncError::DATATYPE_ERROR
,
591 "Failed to create foster parent",
593 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
597 for (int i
= dst
->child_count() - 1; i
>= 0; --i
) {
598 model
->Move(dst
->GetChild(i
), foster_parent
,
599 foster_parent
->child_count());
602 DCHECK_EQ(dst
->child_count(), 0) << "Node being deleted has children";
604 model_associator_
->Disassociate(it
->id
);
606 const BookmarkNode
* parent
= dst
->parent();
607 int index
= parent
->GetIndexOf(dst
);
609 model
->Remove(parent
, index
);
612 // A map to keep track of some reordering work we defer until later.
613 std::multimap
<int, const BookmarkNode
*> to_reposition
;
615 syncer::ReadNode
synced_bookmarks(trans
);
616 int64 synced_bookmarks_id
= syncer::kInvalidId
;
617 if (synced_bookmarks
.InitByTagLookupForBookmarks(kMobileBookmarksTag
) ==
618 syncer::BaseNode::INIT_OK
) {
619 synced_bookmarks_id
= synced_bookmarks
.GetId();
622 // Continue iterating where the previous loop left off.
623 for ( ; it
!= changes
.Get().end(); ++it
) {
624 const BookmarkNode
* dst
=
625 model_associator_
->GetChromeNodeFromSyncId(it
->id
);
627 // Ignore changes to the permanent top-level nodes. We only care about
629 if (model
->is_permanent_node(dst
))
632 // Because the Synced Bookmarks node can be created server side, it's
633 // possible it'll arrive at the client as an update. In that case it won't
634 // have been associated at startup, the GetChromeNodeFromSyncId call above
635 // will return NULL, and we won't detect it as a permanent node, resulting
636 // in us trying to create it here (which will fail). Therefore, we add
637 // special logic here just to detect the Synced Bookmarks folder.
638 if (synced_bookmarks_id
!= syncer::kInvalidId
&&
639 it
->id
== synced_bookmarks_id
) {
640 // This is a newly created Synced Bookmarks node. Associate it.
641 model_associator_
->Associate(model
->mobile_node(), it
->id
);
645 DCHECK_NE(it
->action
, ChangeRecord::ACTION_DELETE
)
646 << "We should have passed all deletes by this point.";
648 syncer::ReadNode
src(trans
);
649 if (src
.InitByIdLookup(it
->id
) != syncer::BaseNode::INIT_OK
) {
650 syncer::SyncError
error(FROM_HERE
,
651 syncer::SyncError::DATATYPE_ERROR
,
652 "Failed to load sync node",
654 error_handler()->OnSingleDataTypeUnrecoverableError(error
);
658 const BookmarkNode
* parent
=
659 model_associator_
->GetChromeNodeFromSyncId(src
.GetParentId());
661 LOG(ERROR
) << "Could not find parent of node being added/updated."
662 << " Node title: " << src
.GetTitle()
663 << ", parent id = " << src
.GetParentId();
668 DCHECK(it
->action
== ChangeRecord::ACTION_UPDATE
)
669 << "ACTION_UPDATE should be seen if and only if the node is known.";
670 UpdateBookmarkWithSyncData(src
, model
, dst
, profile_
);
672 // Move all modified entries to the right. We'll fix it later.
673 model
->Move(dst
, parent
, parent
->child_count());
675 DCHECK(it
->action
== ChangeRecord::ACTION_ADD
)
676 << "ACTION_ADD should be seen if and only if the node is unknown.";
678 dst
= CreateBookmarkNode(&src
,
682 parent
->child_count());
684 // We ignore bookmarks we can't add. Chances are this is caused by
685 // a bookmark that was not fully associated.
686 LOG(ERROR
) << "Failed to create bookmark node with title "
687 << src
.GetTitle() + " and url "
688 << src
.GetBookmarkSpecifics().url();
691 model_associator_
->Associate(dst
, src
.GetId());
694 to_reposition
.insert(std::make_pair(src
.GetPositionIndex(), dst
));
695 bookmark_model_
->SetNodeSyncTransactionVersion(dst
, model_version
);
698 // When we added or updated bookmarks in the previous loop, we placed them to
699 // the far right position. Now we iterate over all these modified items in
700 // sync order, left to right, moving them into their proper positions.
701 for (std::multimap
<int, const BookmarkNode
*>::iterator it
=
702 to_reposition
.begin(); it
!= to_reposition
.end(); ++it
) {
703 const BookmarkNode
* parent
= it
->second
->parent();
704 model
->Move(it
->second
, parent
, it
->first
);
707 // Clean up the temporary node.
709 // There should be no nodes left under the foster parent.
710 DCHECK_EQ(foster_parent
->child_count(), 0);
711 model
->Remove(foster_parent
->parent(),
712 foster_parent
->parent()->GetIndexOf(foster_parent
));
713 foster_parent
= NULL
;
716 // The visibility of the mobile node may need to change.
717 model_associator_
->UpdatePermanentNodeVisibility();
719 // Notify UI intensive observers of BookmarkModel that all updates have been
720 // applied, and that they may now be consumed. This prevents issues like the
721 // one described in crbug.com/281562, where old and new items on the bookmarks
722 // bar would overlap.
723 model
->EndExtensiveChanges();
725 // We are now ready to hear about bookmarks changes again.
726 model
->AddObserver(this);
728 // All changes are applied in bookmark model. Set transaction version on
729 // bookmark model to mark as synced.
730 model
->SetNodeSyncTransactionVersion(model
->root_node(), model_version
);
734 // Update a bookmark node with specified sync data.
735 void BookmarkChangeProcessor::UpdateBookmarkWithSyncData(
736 const syncer::BaseNode
& sync_node
,
737 BookmarkModel
* model
,
738 const BookmarkNode
* node
,
740 DCHECK_EQ(sync_node
.GetIsFolder(), node
->is_folder());
741 const sync_pb::BookmarkSpecifics
& specifics
=
742 sync_node
.GetBookmarkSpecifics();
743 if (!sync_node
.GetIsFolder())
744 model
->SetURL(node
, GURL(specifics
.url()));
745 model
->SetTitle(node
, base::UTF8ToUTF16(sync_node
.GetTitle()));
746 if (specifics
.has_creation_time_us()) {
749 base::Time::FromInternalValue(specifics
.creation_time_us()));
751 SetBookmarkFavicon(&sync_node
, node
, model
, profile
);
752 model
->SetNodeMetaInfoMap(node
, *GetBookmarkMetaInfo(&sync_node
));
756 void BookmarkChangeProcessor::UpdateTransactionVersion(
758 BookmarkModel
* model
,
759 const std::vector
<const BookmarkNode
*>& nodes
) {
760 if (new_version
!= syncer::syncable::kInvalidTransactionVersion
) {
761 model
->SetNodeSyncTransactionVersion(model
->root_node(), new_version
);
762 for (size_t i
= 0; i
< nodes
.size(); ++i
) {
763 model
->SetNodeSyncTransactionVersion(nodes
[i
], new_version
);
769 // Creates a bookmark node under the given parent node from the given sync
770 // node. Returns the newly created node.
771 const BookmarkNode
* BookmarkChangeProcessor::CreateBookmarkNode(
772 syncer::BaseNode
* sync_node
,
773 const BookmarkNode
* parent
,
774 BookmarkModel
* model
,
779 const BookmarkNode
* node
;
780 if (sync_node
->GetIsFolder()) {
782 model
->AddFolderWithMetaInfo(parent
,
784 base::UTF8ToUTF16(sync_node
->GetTitle()),
785 GetBookmarkMetaInfo(sync_node
).get());
787 // 'creation_time_us' was added in m24. Assume a time of 0 means now.
788 const sync_pb::BookmarkSpecifics
& specifics
=
789 sync_node
->GetBookmarkSpecifics();
790 const int64 create_time_internal
= specifics
.creation_time_us();
791 base::Time create_time
= (create_time_internal
== 0) ?
792 base::Time::Now() : base::Time::FromInternalValue(create_time_internal
);
793 node
= model
->AddURLWithCreationTimeAndMetaInfo(
796 base::UTF8ToUTF16(sync_node
->GetTitle()),
797 GURL(specifics
.url()),
799 GetBookmarkMetaInfo(sync_node
).get());
801 SetBookmarkFavicon(sync_node
, node
, model
, profile
);
808 // Sets the favicon of the given bookmark node from the given sync node.
809 bool BookmarkChangeProcessor::SetBookmarkFavicon(
810 const syncer::BaseNode
* sync_node
,
811 const BookmarkNode
* bookmark_node
,
812 BookmarkModel
* bookmark_model
,
814 const sync_pb::BookmarkSpecifics
& specifics
=
815 sync_node
->GetBookmarkSpecifics();
816 const std::string
& icon_bytes_str
= specifics
.favicon();
817 if (icon_bytes_str
.empty())
820 scoped_refptr
<base::RefCountedString
> icon_bytes(
821 new base::RefCountedString());
822 icon_bytes
->data().assign(icon_bytes_str
);
823 GURL
icon_url(specifics
.icon_url());
825 // Old clients may not be syncing the favicon URL. If the icon URL is not
826 // synced, use the page URL as a fake icon URL as it is guaranteed to be
828 if (icon_url
.is_empty())
829 icon_url
= bookmark_node
->url();
831 ApplyBookmarkFavicon(bookmark_node
, profile
, icon_url
, icon_bytes
);
837 scoped_ptr
<BookmarkNode::MetaInfoMap
>
838 BookmarkChangeProcessor::GetBookmarkMetaInfo(
839 const syncer::BaseNode
* sync_node
) {
840 const sync_pb::BookmarkSpecifics
& specifics
=
841 sync_node
->GetBookmarkSpecifics();
842 scoped_ptr
<BookmarkNode::MetaInfoMap
> meta_info_map(
843 new BookmarkNode::MetaInfoMap
);
844 for (int i
= 0; i
< specifics
.meta_info_size(); ++i
) {
845 (*meta_info_map
)[specifics
.meta_info(i
).key()] =
846 specifics
.meta_info(i
).value();
848 // Verifies that all entries had unique keys.
849 DCHECK_EQ(static_cast<size_t>(specifics
.meta_info_size()),
850 meta_info_map
->size());
851 return meta_info_map
.Pass();
855 void BookmarkChangeProcessor::SetSyncNodeMetaInfo(
856 const BookmarkNode
* node
,
857 syncer::WriteNode
* sync_node
) {
858 sync_pb::BookmarkSpecifics specifics
= sync_node
->GetBookmarkSpecifics();
859 const BookmarkNode::MetaInfoMap
* meta_info_map
= node
->GetMetaInfoMap();
861 // Compare specifics meta info to node meta info before making the change.
862 // Please note that the original specifics meta info is unordered while
863 // meta_info_map is ordered by key. Setting the meta info blindly into
864 // the specifics might cause an unnecessary change.
865 size_t size
= meta_info_map
? meta_info_map
->size() : 0;
866 if (static_cast<size_t>(specifics
.meta_info_size()) == size
) {
868 for (; index
< size
; index
++) {
869 const sync_pb::MetaInfo
& meta_info
= specifics
.meta_info(index
);
870 BookmarkNode::MetaInfoMap::const_iterator it
=
871 meta_info_map
->find(meta_info
.key());
872 if (it
== meta_info_map
->end() || it
->second
!= meta_info
.value()) {
873 // One of original meta info entries is missing in |meta_info_map| or
879 // The original meta info from the sync model is already equivalent to
885 // Clear and reset meta info in bookmark specifics.
886 specifics
.clear_meta_info();
888 for (BookmarkNode::MetaInfoMap::const_iterator it
= meta_info_map
->begin();
889 it
!= meta_info_map
->end(); ++it
) {
890 sync_pb::MetaInfo
* meta_info
= specifics
.add_meta_info();
891 meta_info
->set_key(it
->first
);
892 meta_info
->set_value(it
->second
);
896 sync_node
->SetBookmarkSpecifics(specifics
);
900 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
901 const BookmarkNode
* bookmark_node
,
903 const GURL
& icon_url
,
904 const scoped_refptr
<base::RefCountedMemory
>& bitmap_data
) {
905 history::HistoryService
* history
= HistoryServiceFactory::GetForProfile(
906 profile
, ServiceAccessType::EXPLICIT_ACCESS
);
907 favicon::FaviconService
* favicon_service
=
908 FaviconServiceFactory::GetForProfile(profile
,
909 ServiceAccessType::EXPLICIT_ACCESS
);
911 history
->AddPageNoVisitForBookmark(bookmark_node
->url(),
912 bookmark_node
->GetTitle());
913 // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
914 // overwrite the cached 2x favicon bitmap. Sync favicons are always
915 // gfx::kFaviconSize in width and height. Store the favicon into history
917 gfx::Size
pixel_size(gfx::kFaviconSize
, gfx::kFaviconSize
);
918 favicon_service
->MergeFavicon(bookmark_node
->url(),
920 favicon_base::FAVICON
,
926 void BookmarkChangeProcessor::SetSyncNodeFavicon(
927 const BookmarkNode
* bookmark_node
,
928 BookmarkModel
* model
,
929 syncer::WriteNode
* sync_node
) {
930 scoped_refptr
<base::RefCountedMemory
> favicon_bytes(NULL
);
931 EncodeFavicon(bookmark_node
, model
, &favicon_bytes
);
932 if (favicon_bytes
.get() && favicon_bytes
->size()) {
933 sync_pb::BookmarkSpecifics
updated_specifics(
934 sync_node
->GetBookmarkSpecifics());
935 updated_specifics
.set_favicon(favicon_bytes
->front(),
936 favicon_bytes
->size());
937 updated_specifics
.set_icon_url(bookmark_node
->icon_url().spec());
938 sync_node
->SetBookmarkSpecifics(updated_specifics
);
942 bool BookmarkChangeProcessor::CanSyncNode(const BookmarkNode
* node
) {
943 return bookmark_model_
->client()->CanSyncNode(node
);
946 } // namespace browser_sync