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"
10 #include "base/location.h"
11 #include "base/string16.h"
12 #include "base/string_util.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/browser/bookmarks/bookmark_model.h"
16 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
17 #include "chrome/browser/bookmarks/bookmark_utils.h"
18 #include "chrome/browser/favicon/favicon_service.h"
19 #include "chrome/browser/favicon/favicon_service_factory.h"
20 #include "chrome/browser/history/history_service.h"
21 #include "chrome/browser/history/history_service_factory.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/sync/profile_sync_service.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "sync/internal_api/public/change_record.h"
26 #include "sync/internal_api/public/read_node.h"
27 #include "sync/internal_api/public/write_node.h"
28 #include "sync/internal_api/public/write_transaction.h"
29 #include "sync/syncable/entry.h" // TODO(tim): Investigating bug 121587.
30 #include "sync/syncable/syncable_write_transaction.h"
31 #include "ui/gfx/favicon_size.h"
32 #include "ui/gfx/image/image_util.h"
34 using content::BrowserThread
;
36 namespace browser_sync
{
38 static const char kMobileBookmarksTag
[] = "synced_bookmarks";
40 // Key for sync transaction version in bookmark node meta info.
41 const char kBookmarkTransactionVersionKey
[] = "sync.transaction_version";
43 BookmarkChangeProcessor::BookmarkChangeProcessor(
44 BookmarkModelAssociator
* model_associator
,
45 DataTypeErrorHandler
* error_handler
)
46 : ChangeProcessor(error_handler
),
47 bookmark_model_(NULL
),
48 model_associator_(model_associator
) {
49 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
50 DCHECK(model_associator
);
51 DCHECK(error_handler
);
54 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
56 bookmark_model_
->RemoveObserver(this);
59 void BookmarkChangeProcessor::StartImpl(Profile
* profile
) {
60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
63 DCHECK(!bookmark_model_
);
64 bookmark_model_
= BookmarkModelFactory::GetForProfile(profile
);
65 DCHECK(bookmark_model_
->IsLoaded());
66 bookmark_model_
->AddObserver(this);
69 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
70 const BookmarkNode
* src
,
72 syncer::WriteNode
* dst
) {
73 // Set the properties of the item.
74 dst
->SetIsFolder(src
->is_folder());
75 dst
->SetTitle(UTF16ToWideHack(src
->GetTitle()));
76 sync_pb::BookmarkSpecifics
bookmark_specifics(dst
->GetBookmarkSpecifics());
77 if (!src
->is_folder())
78 bookmark_specifics
.set_url(src
->url().spec());
79 bookmark_specifics
.set_creation_time_us(src
->date_added().ToInternalValue());
80 dst
->SetBookmarkSpecifics(bookmark_specifics
);
81 SetSyncNodeFavicon(src
, model
, dst
);
85 void BookmarkChangeProcessor::EncodeFavicon(
86 const BookmarkNode
* src
,
88 scoped_refptr
<base::RefCountedMemory
>* dst
) {
89 const gfx::Image
& favicon
= model
->GetFavicon(src
);
91 // Check for empty images. This can happen if the favicon is
92 // still being loaded.
93 if (favicon
.IsEmpty())
96 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
98 *dst
= favicon
.As1xPNGBytes();
101 void BookmarkChangeProcessor::RemoveOneSyncNode(syncer::WriteNode
* sync_node
) {
102 // This node should have no children.
103 DCHECK(!sync_node
->HasChildren());
104 // Remove association and delete the sync node.
105 model_associator_
->Disassociate(sync_node
->GetId());
106 sync_node
->Tombstone();
109 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
110 const BookmarkNode
* topmost
) {
112 syncer::syncable::kInvalidTransactionVersion
;
114 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
115 syncer::WriteNode
topmost_sync_node(&trans
);
116 if (!model_associator_
->InitSyncNodeFromChromeId(topmost
->id(),
117 &topmost_sync_node
)) {
118 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
122 // Check that |topmost| has been unlinked.
123 DCHECK(topmost
->is_root());
124 RemoveAllChildNodes(&trans
, topmost
->id());
125 // Remove the node itself.
126 RemoveOneSyncNode(&topmost_sync_node
);
129 // Don't need to update versions of deleted nodes.
130 UpdateTransactionVersion(new_version
, bookmark_model_
,
131 std::vector
<const BookmarkNode
*>());
134 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
135 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
137 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
139 RemoveAllChildNodes(&trans
, bookmark_model_
->bookmark_bar_node()->id());
140 RemoveAllChildNodes(&trans
, bookmark_model_
->other_node()->id());
141 // Remove mobile bookmarks node only if it is present.
142 const int64 mobile_bookmark_id
= bookmark_model_
->mobile_node()->id();
143 if (model_associator_
->GetSyncIdFromChromeId(mobile_bookmark_id
) !=
144 syncer::kInvalidId
) {
145 RemoveAllChildNodes(&trans
, bookmark_model_
->mobile_node()->id());
149 // Don't need to update versions of deleted nodes.
150 UpdateTransactionVersion(new_version
, bookmark_model_
,
151 std::vector
<const BookmarkNode
*>());
154 void BookmarkChangeProcessor::RemoveAllChildNodes(
155 syncer::WriteTransaction
* trans
, const int64
& topmost_node_id
) {
156 syncer::WriteNode
topmost_node(trans
);
157 if (!model_associator_
->InitSyncNodeFromChromeId(topmost_node_id
,
159 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
163 const int64 topmost_sync_id
= topmost_node
.GetId();
165 // Do a DFS and delete all the child sync nodes, use sync id instead of
166 // bookmark node ids since the bookmark nodes may already be deleted.
167 // The equivalent recursive version of this iterative DFS:
168 // remove_all_children(node_id, topmost_node_id):
169 // node.initByIdLookup(node_id)
170 // while(node.GetFirstChildId() != syncer::kInvalidId)
171 // remove_all_children(node.GetFirstChildId(), topmost_node_id)
172 // if(node_id != topmost_node_id)
175 std::stack
<int64
> dfs_sync_id_stack
;
176 // Push the topmost node.
177 dfs_sync_id_stack
.push(topmost_sync_id
);
178 while (!dfs_sync_id_stack
.empty()) {
179 const int64 sync_node_id
= dfs_sync_id_stack
.top();
180 syncer::WriteNode
node(trans
);
181 node
.InitByIdLookup(sync_node_id
);
182 if (!node
.GetIsFolder() || node
.GetFirstChildId() == syncer::kInvalidId
) {
183 // All children of the node has been processed, delete the node and
184 // pop it off the stack.
185 dfs_sync_id_stack
.pop();
186 // Do not delete the topmost node.
187 if (sync_node_id
!= topmost_sync_id
) {
188 RemoveOneSyncNode(&node
);
190 // if we are processing topmost node, all other nodes must be processed
191 // the stack should be empty.
192 DCHECK(dfs_sync_id_stack
.empty());
195 int64 child_id
= node
.GetFirstChildId();
196 if (child_id
!= syncer::kInvalidId
) {
197 dfs_sync_id_stack
.push(child_id
);
203 void BookmarkChangeProcessor::Loaded(BookmarkModel
* model
,
204 bool ids_reassigned
) {
208 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
209 BookmarkModel
* model
) {
211 bookmark_model_
= NULL
;
214 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel
* model
,
215 const BookmarkNode
* parent
,
217 DCHECK(share_handle());
219 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
220 int64 sync_id
= syncer::kInvalidId
;
222 // Acquire a scoped write lock via a transaction.
223 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
224 sync_id
= CreateSyncNode(parent
, model
, index
, &trans
,
225 model_associator_
, error_handler());
228 if (syncer::kInvalidId
!= sync_id
) {
229 // Siblings of added node in sync DB will also be updated to reflect new
230 // PREV_ID/NEXT_ID and thus get a new version. But we only update version
231 // of added node here. After switching to ordinals for positioning,
232 // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
233 UpdateTransactionVersion(
235 std::vector
<const BookmarkNode
*>(1, parent
->GetChild(index
)));
240 int64
BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode
* parent
,
241 BookmarkModel
* model
, int index
, syncer::WriteTransaction
* trans
,
242 BookmarkModelAssociator
* associator
,
243 DataTypeErrorHandler
* error_handler
) {
244 const BookmarkNode
* child
= parent
->GetChild(index
);
247 // Create a WriteNode container to hold the new node.
248 syncer::WriteNode
sync_child(trans
);
250 // Actually create the node with the appropriate initial position.
251 if (!PlaceSyncNode(CREATE
, parent
, index
, trans
, &sync_child
, associator
)) {
252 error_handler
->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
253 "Sync node creation failed; recovery unlikely");
254 return syncer::kInvalidId
;
257 UpdateSyncNodeProperties(child
, model
, &sync_child
);
259 // Associate the ID from the sync domain with the bookmark node, so that we
260 // can refer back to this item later.
261 associator
->Associate(child
, sync_child
.GetId());
263 return sync_child
.GetId();
266 void BookmarkChangeProcessor::BookmarkNodeRemoved(BookmarkModel
* model
,
267 const BookmarkNode
* parent
,
269 const BookmarkNode
* node
) {
270 RemoveSyncNodeHierarchy(node
);
273 void BookmarkChangeProcessor::BookmarkAllNodesRemoved(BookmarkModel
* model
) {
274 RemoveAllSyncNodes();
277 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel
* model
,
278 const BookmarkNode
* node
) {
279 // We shouldn't see changes to the top-level nodes.
280 if (model
->is_permanent_node(node
)) {
281 NOTREACHED() << "Saw update to permanent node!";
285 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
287 // Acquire a scoped write lock via a transaction.
288 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
290 // Lookup the sync node that's associated with |node|.
291 syncer::WriteNode
sync_node(&trans
);
292 if (!model_associator_
->InitSyncNodeFromChromeId(node
->id(), &sync_node
)) {
293 // TODO(tim): Investigating bug 121587.
294 if (model_associator_
->GetSyncIdFromChromeId(node
->id()) ==
295 syncer::kInvalidId
) {
296 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
297 "Bookmark id not found in model associator on BookmarkNodeChanged");
298 LOG(ERROR
) << "Bad id.";
299 } else if (!sync_node
.GetEntry()->good()) {
300 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
301 "Could not InitByIdLookup on BookmarkNodeChanged, good() failed");
302 LOG(ERROR
) << "Bad entry.";
303 } else if (sync_node
.GetEntry()->Get(syncer::syncable::IS_DEL
)) {
304 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
305 "Could not InitByIdLookup on BookmarkNodeChanged, is_del true");
306 LOG(ERROR
) << "Deleted entry.";
308 syncer::Cryptographer
* crypto
= trans
.GetCryptographer();
309 syncer::ModelTypeSet
encrypted_types(trans
.GetEncryptedTypes());
310 const sync_pb::EntitySpecifics
& specifics
=
311 sync_node
.GetEntry()->Get(syncer::syncable::SPECIFICS
);
312 CHECK(specifics
.has_encrypted());
313 const bool can_decrypt
= crypto
->CanDecrypt(specifics
.encrypted());
314 const bool agreement
= encrypted_types
.Has(syncer::BOOKMARKS
);
315 if (!agreement
&& !can_decrypt
) {
316 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
317 "Could not InitByIdLookup on BookmarkNodeChanged, "
318 " Cryptographer thinks bookmarks not encrypted, and CanDecrypt"
320 LOG(ERROR
) << "Case 1.";
321 } else if (agreement
&& can_decrypt
) {
322 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
323 "Could not InitByIdLookup on BookmarkNodeChanged, "
324 " Cryptographer thinks bookmarks are encrypted, and CanDecrypt"
325 " succeeded (?!), but DecryptIfNecessary failed.");
326 LOG(ERROR
) << "Case 2.";
327 } else if (agreement
) {
328 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
329 "Could not InitByIdLookup on BookmarkNodeChanged, "
330 " Cryptographer thinks bookmarks are encrypted, but CanDecrypt"
332 LOG(ERROR
) << "Case 3.";
334 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
335 "Could not InitByIdLookup on BookmarkNodeChanged, "
336 " Cryptographer thinks bookmarks not encrypted, but CanDecrypt"
337 " succeeded (super weird, btw)");
338 LOG(ERROR
) << "Case 4.";
344 UpdateSyncNodeProperties(node
, model
, &sync_node
);
346 DCHECK_EQ(sync_node
.GetIsFolder(), node
->is_folder());
347 DCHECK_EQ(model_associator_
->GetChromeNodeFromSyncId(
348 sync_node
.GetParentId()),
350 // This node's index should be one more than the predecessor's index.
351 DCHECK_EQ(node
->parent()->GetIndexOf(node
),
352 CalculateBookmarkModelInsertionIndex(node
->parent(),
357 UpdateTransactionVersion(new_version
, model
,
358 std::vector
<const BookmarkNode
*>(1, node
));
361 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel
* model
,
362 const BookmarkNode
* old_parent
, int old_index
,
363 const BookmarkNode
* new_parent
, int new_index
) {
364 const BookmarkNode
* child
= new_parent
->GetChild(new_index
);
365 // We shouldn't see changes to the top-level nodes.
366 if (model
->is_permanent_node(child
)) {
367 NOTREACHED() << "Saw update to permanent node!";
371 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
373 // Acquire a scoped write lock via a transaction.
374 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
376 // Lookup the sync node that's associated with |child|.
377 syncer::WriteNode
sync_node(&trans
);
378 if (!model_associator_
->InitSyncNodeFromChromeId(child
->id(), &sync_node
)) {
379 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
384 if (!PlaceSyncNode(MOVE
, new_parent
, new_index
, &trans
, &sync_node
,
385 model_associator_
)) {
386 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
392 UpdateTransactionVersion(new_version
, model
,
393 std::vector
<const BookmarkNode
*>(1, child
));
396 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
397 BookmarkModel
* model
,
398 const BookmarkNode
* node
) {
399 BookmarkNodeChanged(model
, node
);
402 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
403 BookmarkModel
* model
, const BookmarkNode
* node
) {
404 int64 new_version
= syncer::syncable::kInvalidTransactionVersion
;
405 std::vector
<const BookmarkNode
*> children
;
407 // Acquire a scoped write lock via a transaction.
408 syncer::WriteTransaction
trans(FROM_HERE
, share_handle(), &new_version
);
410 // The given node's children got reordered. We need to reorder all the
411 // children of the corresponding sync node.
412 for (int i
= 0; i
< node
->child_count(); ++i
) {
413 const BookmarkNode
* child
= node
->GetChild(i
);
414 children
.push_back(child
);
416 syncer::WriteNode
sync_child(&trans
);
417 if (!model_associator_
->InitSyncNodeFromChromeId(child
->id(),
419 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
423 DCHECK_EQ(sync_child
.GetParentId(),
424 model_associator_
->GetSyncIdFromChromeId(node
->id()));
426 if (!PlaceSyncNode(MOVE
, node
, i
, &trans
, &sync_child
,
427 model_associator_
)) {
428 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
435 // TODO(haitaol): Filter out children that didn't actually change.
436 UpdateTransactionVersion(new_version
, model
, children
);
440 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation
,
441 const BookmarkNode
* parent
, int index
, syncer::WriteTransaction
* trans
,
442 syncer::WriteNode
* dst
, BookmarkModelAssociator
* associator
) {
443 syncer::ReadNode
sync_parent(trans
);
444 if (!associator
->InitSyncNodeFromChromeId(parent
->id(), &sync_parent
)) {
445 LOG(WARNING
) << "Parent lookup failed";
449 bool success
= false;
451 // Insert into first position.
452 success
= (operation
== CREATE
) ?
453 dst
->InitBookmarkByCreation(sync_parent
, NULL
) :
454 dst
->SetPosition(sync_parent
, NULL
);
456 DCHECK_EQ(dst
->GetParentId(), sync_parent
.GetId());
457 DCHECK_EQ(dst
->GetId(), sync_parent
.GetFirstChildId());
458 DCHECK_EQ(dst
->GetPredecessorId(), syncer::kInvalidId
);
461 // Find the bookmark model predecessor, and insert after it.
462 const BookmarkNode
* prev
= parent
->GetChild(index
- 1);
463 syncer::ReadNode
sync_prev(trans
);
464 if (!associator
->InitSyncNodeFromChromeId(prev
->id(), &sync_prev
)) {
465 LOG(WARNING
) << "Predecessor lookup failed";
468 success
= (operation
== CREATE
) ?
469 dst
->InitBookmarkByCreation(sync_parent
, &sync_prev
) :
470 dst
->SetPosition(sync_parent
, &sync_prev
);
472 DCHECK_EQ(dst
->GetParentId(), sync_parent
.GetId());
473 DCHECK_EQ(dst
->GetPredecessorId(), sync_prev
.GetId());
474 DCHECK_EQ(dst
->GetId(), sync_prev
.GetSuccessorId());
481 // Determine the bookmark model index to which a node must be moved so that
482 // predecessor of the node (in the bookmark model) matches the predecessor of
483 // |source| (in the sync model).
484 // As a precondition, this assumes that the predecessor of |source| has been
485 // updated and is already in the correct position in the bookmark model.
486 int BookmarkChangeProcessor::CalculateBookmarkModelInsertionIndex(
487 const BookmarkNode
* parent
,
488 const syncer::BaseNode
* child_info
,
489 BookmarkModelAssociator
* model_associator
) {
492 int64 predecessor_id
= child_info
->GetPredecessorId();
493 // A return ID of kInvalidId indicates no predecessor.
494 if (predecessor_id
== syncer::kInvalidId
)
497 // Otherwise, insert after the predecessor bookmark node.
498 const BookmarkNode
* predecessor
=
499 model_associator
->GetChromeNodeFromSyncId(predecessor_id
);
501 DCHECK_EQ(predecessor
->parent(), parent
);
502 return parent
->GetIndexOf(predecessor
) + 1;
505 // ApplyModelChanges is called by the sync backend after changes have been made
506 // to the sync engine's model. Apply these changes to the browser bookmark
508 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
509 const syncer::BaseTransaction
* trans
,
511 const syncer::ImmutableChangeRecordList
& changes
) {
512 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
513 // A note about ordering. Sync backend is responsible for ordering the change
514 // records in the following order:
516 // 1. Deletions, from leaves up to parents.
517 // 2. Existing items with synced parents & predecessors.
518 // 3. New items with synced parents & predecessors.
519 // 4. Items with parents & predecessors in the list.
520 // 5. Repeat #4 until all items are in the list.
522 // "Predecessor" here means the previous item within a given folder; an item
523 // in the first position is always said to have a synced predecessor.
524 // For the most part, applying these changes in the order given will yield
525 // the correct result. There is one exception, however: for items that are
526 // moved away from a folder that is being deleted, we will process the delete
527 // before the move. Since deletions in the bookmark model propagate from
528 // parent to child, we must move them to a temporary location.
529 BookmarkModel
* model
= bookmark_model_
;
531 // We are going to make changes to the bookmarks model, but don't want to end
532 // up in a feedback loop, so remove ourselves as an observer while applying
534 model
->RemoveObserver(this);
536 // A parent to hold nodes temporarily orphaned by parent deletion. It is
537 // lazily created inside the loop.
538 const BookmarkNode
* foster_parent
= NULL
;
540 // Whether we have passed all the deletes (which should be at the
541 // front of the list).
542 bool passed_deletes
= false;
543 for (syncer::ChangeRecordList::const_iterator it
=
544 changes
.Get().begin(); it
!= changes
.Get().end(); ++it
) {
545 const BookmarkNode
* dst
=
546 model_associator_
->GetChromeNodeFromSyncId(it
->id
);
547 // Ignore changes to the permanent top-level nodes. We only care about
549 if (model
->is_permanent_node(dst
))
552 syncer::ChangeRecord::ACTION_DELETE
) {
553 // Deletions should always be at the front of the list.
554 DCHECK(!passed_deletes
);
555 // Children of a deleted node should not be deleted; they may be
556 // reparented by a later change record. Move them to a temporary place.
557 if (!dst
) // Can't do anything if we can't find the chrome node.
559 const BookmarkNode
* parent
= dst
->parent();
561 if (!foster_parent
) {
562 foster_parent
= model
->AddFolder(model
->other_node(),
563 model
->other_node()->child_count(),
565 if (!foster_parent
) {
566 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
567 "Failed to create foster parent.");
571 for (int i
= dst
->child_count() - 1; i
>= 0; --i
) {
572 model
->Move(dst
->GetChild(i
), foster_parent
,
573 foster_parent
->child_count());
576 DCHECK_EQ(dst
->child_count(), 0) << "Node being deleted has children";
577 model_associator_
->Disassociate(it
->id
);
578 int index
= parent
->GetIndexOf(dst
);
580 model
->Remove(parent
, index
);
583 DCHECK_EQ((it
->action
==
584 syncer::ChangeRecord::ACTION_ADD
), (dst
== NULL
))
585 << "ACTION_ADD should be seen if and only if the node is unknown.";
586 passed_deletes
= true;
588 syncer::ReadNode
src(trans
);
589 if (src
.InitByIdLookup(it
->id
) != syncer::BaseNode::INIT_OK
) {
590 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE
,
591 "ApplyModelChanges was passed a bad ID");
595 const BookmarkNode
* node
= CreateOrUpdateBookmarkNode(&src
, model
,
599 bookmark_model_
->SetNodeMetaInfo(node
, kBookmarkTransactionVersionKey
,
600 base::Int64ToString(model_version
));
602 // Because the Synced Bookmarks node can be created server side, it's
603 // possible it'll arrive at the client as an update. In that case it
604 // won't have been associated at startup, the GetChromeNodeFromSyncId
605 // call above will return NULL, and we won't detect it as a permanent
606 // node, resulting in us trying to create it here (which will
607 // fail). Therefore, we add special logic here just to detect the
608 // Synced Bookmarks folder.
609 syncer::ReadNode
synced_bookmarks(trans
);
610 if (synced_bookmarks
.InitByTagLookup(kMobileBookmarksTag
) ==
611 syncer::BaseNode::INIT_OK
&&
612 synced_bookmarks
.GetId() == it
->id
) {
613 // This is a newly created Synced Bookmarks node. Associate it.
614 model_associator_
->Associate(model
->mobile_node(), it
->id
);
616 // We ignore bookmarks we can't add. Chances are this is caused by
617 // a bookmark that was not fully associated.
618 LOG(ERROR
) << "Failed to create bookmark node with title "
619 << src
.GetTitle() + " and url "
620 << src
.GetBookmarkSpecifics().url();
625 // Clean up the temporary node.
627 // There should be no nodes left under the foster parent.
628 DCHECK_EQ(foster_parent
->child_count(), 0);
629 model
->Remove(foster_parent
->parent(),
630 foster_parent
->parent()->GetIndexOf(foster_parent
));
631 foster_parent
= NULL
;
634 // The visibility of the mobile node may need to change.
635 model_associator_
->UpdatePermanentNodeVisibility();
637 // We are now ready to hear about bookmarks changes again.
638 model
->AddObserver(this);
640 // All changes are applied in bookmark model. Set transaction version on
641 // bookmark model to mark as synced.
642 model
->SetNodeMetaInfo(model
->root_node(), kBookmarkTransactionVersionKey
,
643 base::Int64ToString(model_version
));
647 // Create a bookmark node corresponding to |src| if one is not already
648 // associated with |src|.
649 const BookmarkNode
* BookmarkChangeProcessor::CreateOrUpdateBookmarkNode(
650 syncer::BaseNode
* src
,
651 BookmarkModel
* model
,
653 BookmarkModelAssociator
* model_associator
) {
654 const BookmarkNode
* parent
=
655 model_associator
->GetChromeNodeFromSyncId(src
->GetParentId());
657 DLOG(WARNING
) << "Could not find parent of node being added/updated."
658 << " Node title: " << src
->GetTitle()
659 << ", parent id = " << src
->GetParentId();
663 int index
= CalculateBookmarkModelInsertionIndex(parent
,
666 const BookmarkNode
* dst
= model_associator
->GetChromeNodeFromSyncId(
669 dst
= CreateBookmarkNode(src
, parent
, model
, profile
, index
);
671 model_associator
->Associate(dst
, src
->GetId());
673 // URL and is_folder are not expected to change.
674 // TODO(ncarter): Determine if such changes should be legal or not.
675 DCHECK_EQ(src
->GetIsFolder(), dst
->is_folder());
677 // Handle reparenting and/or repositioning.
678 model
->Move(dst
, parent
, index
);
680 const sync_pb::BookmarkSpecifics
& specifics
= src
->GetBookmarkSpecifics();
681 if (!src
->GetIsFolder())
682 model
->SetURL(dst
, GURL(specifics
.url()));
683 model
->SetTitle(dst
, UTF8ToUTF16(src
->GetTitle()));
684 if (specifics
.has_creation_time_us()) {
685 model
->SetDateAdded(dst
,
686 base::Time::FromInternalValue(
687 specifics
.creation_time_us()));
690 SetBookmarkFavicon(src
, dst
, model
, profile
);
697 void BookmarkChangeProcessor::UpdateTransactionVersion(
699 BookmarkModel
* model
,
700 const std::vector
<const BookmarkNode
*>& nodes
) {
701 if (new_version
!= syncer::syncable::kInvalidTransactionVersion
) {
702 model
->SetNodeMetaInfo(model
->root_node(), kBookmarkTransactionVersionKey
,
703 base::Int64ToString(new_version
));
704 for (size_t i
= 0; i
< nodes
.size(); ++i
) {
705 model
->SetNodeMetaInfo(nodes
[i
], kBookmarkTransactionVersionKey
,
706 base::Int64ToString(new_version
));
712 // Creates a bookmark node under the given parent node from the given sync
713 // node. Returns the newly created node.
714 const BookmarkNode
* BookmarkChangeProcessor::CreateBookmarkNode(
715 syncer::BaseNode
* sync_node
,
716 const BookmarkNode
* parent
,
717 BookmarkModel
* model
,
721 DCHECK(index
>= 0 && index
<= parent
->child_count());
723 const BookmarkNode
* node
;
724 if (sync_node
->GetIsFolder()) {
725 node
= model
->AddFolder(parent
, index
,
726 UTF8ToUTF16(sync_node
->GetTitle()));
728 // 'creation_time_us' was added in m24. Assume a time of 0 means now.
729 const sync_pb::BookmarkSpecifics
& specifics
=
730 sync_node
->GetBookmarkSpecifics();
731 const int64 create_time_internal
= specifics
.creation_time_us();
732 base::Time create_time
= (create_time_internal
== 0) ?
733 base::Time::Now() : base::Time::FromInternalValue(create_time_internal
);
734 node
= model
->AddURLWithCreationTime(parent
, index
,
735 UTF8ToUTF16(sync_node
->GetTitle()),
736 GURL(specifics
.url()), create_time
);
738 SetBookmarkFavicon(sync_node
, node
, model
, profile
);
744 // Sets the favicon of the given bookmark node from the given sync node.
745 bool BookmarkChangeProcessor::SetBookmarkFavicon(
746 syncer::BaseNode
* sync_node
,
747 const BookmarkNode
* bookmark_node
,
748 BookmarkModel
* bookmark_model
,
750 const sync_pb::BookmarkSpecifics
& specifics
=
751 sync_node
->GetBookmarkSpecifics();
752 const std::string
& icon_bytes_str
= specifics
.favicon();
753 if (icon_bytes_str
.empty())
756 scoped_refptr
<base::RefCountedString
> icon_bytes(
757 new base::RefCountedString());
758 icon_bytes
->data().assign(icon_bytes_str
);
759 GURL
icon_url(specifics
.icon_url());
761 // Old clients may not be syncing the favicon URL. If the icon URL is not
762 // synced, use the page URL as a fake icon URL as it is guaranteed to be
764 if (icon_url
.is_empty())
765 icon_url
= bookmark_node
->url();
767 ApplyBookmarkFavicon(bookmark_node
, profile
, icon_url
, icon_bytes
);
773 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
774 const BookmarkNode
* bookmark_node
,
776 const GURL
& icon_url
,
777 const scoped_refptr
<base::RefCountedMemory
>& bitmap_data
) {
778 HistoryService
* history
=
779 HistoryServiceFactory::GetForProfile(profile
, Profile::EXPLICIT_ACCESS
);
780 FaviconService
* favicon_service
=
781 FaviconServiceFactory::GetForProfile(profile
, Profile::EXPLICIT_ACCESS
);
783 history
->AddPageNoVisitForBookmark(bookmark_node
->url(),
784 bookmark_node
->GetTitle());
785 // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
786 // overwrite the cached 2x favicon bitmap. Sync favicons are always
787 // gfx::kFaviconSize in width and height. Store the favicon into history
789 gfx::Size
pixel_size(gfx::kFaviconSize
, gfx::kFaviconSize
);
790 favicon_service
->MergeFavicon(bookmark_node
->url(),
798 void BookmarkChangeProcessor::SetSyncNodeFavicon(
799 const BookmarkNode
* bookmark_node
,
800 BookmarkModel
* model
,
801 syncer::WriteNode
* sync_node
) {
802 scoped_refptr
<base::RefCountedMemory
> favicon_bytes(NULL
);
803 EncodeFavicon(bookmark_node
, model
, &favicon_bytes
);
804 if (favicon_bytes
.get() && favicon_bytes
->size()) {
805 sync_pb::BookmarkSpecifics
updated_specifics(
806 sync_node
->GetBookmarkSpecifics());
807 updated_specifics
.set_favicon(favicon_bytes
->front(),
808 favicon_bytes
->size());
809 updated_specifics
.set_icon_url(bookmark_node
->icon_url().spec());
810 sync_node
->SetBookmarkSpecifics(updated_specifics
);
814 } // namespace browser_sync