Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / sync / glue / bookmark_change_processor.cc
blob32db1f997ea77c5dbda8ae0c4e9fc22e671aca39
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"
7 #include <map>
8 #include <stack>
9 #include <vector>
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(
50 Profile* profile,
51 BookmarkModelAssociator* model_associator,
52 sync_driver::DataTypeErrorHandler* error_handler)
53 : sync_driver::ChangeProcessor(error_handler),
54 bookmark_model_(NULL),
55 profile_(profile),
56 model_associator_(model_associator) {
57 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
58 DCHECK(model_associator);
59 DCHECK(profile);
60 DCHECK(error_handler);
63 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
64 if (bookmark_model_)
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,
78 BookmarkModel* model,
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);
92 // static
93 void BookmarkChangeProcessor::EncodeFavicon(
94 const BookmarkNode* src,
95 BookmarkModel* model,
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())
102 return;
104 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
105 // sync subsystem.
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) {
119 int64 new_version =
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",
129 syncer::BOOKMARKS);
130 error_handler()->OnSingleDataTypeUnrecoverableError(error);
131 return;
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,
171 &topmost_node)) {
172 syncer::SyncError error(FROM_HERE,
173 syncer::SyncError::DATATYPE_ERROR,
174 "Failed to init sync node from chrome node",
175 syncer::BOOKMARKS);
176 error_handler()->OnSingleDataTypeUnrecoverableError(error);
177 return;
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)
189 // delete node
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);
205 } else {
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());
210 } else {
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)) {
221 NOTREACHED();
222 return;
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) {
235 UpdateSyncNode(
236 node, bookmark_model_, &trans, model_associator_, error_handler());
237 } else {
238 sync_id = CreateSyncNode(parent,
239 bookmark_model_,
240 index,
241 &trans,
242 model_associator_,
243 error_handler());
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(
253 new_version,
254 bookmark_model_,
255 std::vector<const BookmarkNode*>(1, parent->GetChild(index)));
259 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
260 bool ids_reassigned) {
261 NOTREACHED();
264 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) {
265 NOTREACHED();
266 bookmark_model_ = NULL;
269 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
270 const BookmarkNode* parent,
271 int index) {
272 DCHECK(share_handle());
273 const BookmarkNode* node = parent->GetChild(index);
274 if (CanSyncNode(node))
275 CreateOrUpdateSyncNode(node);
278 // static
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);
284 DCHECK(child);
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.",
294 syncer::BOOKMARKS);
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,
311 int index,
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))
327 return;
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!";
331 return;
333 CreateOrUpdateSyncNode(node);
336 // Static.
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",
349 syncer::BOOKMARKS);
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()),
356 node->parent());
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))
372 return;
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!";
377 return;
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",
391 syncer::BOOKMARKS);
392 error_handler()->OnSingleDataTypeUnrecoverableError(error);
393 return;
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",
401 syncer::BOOKMARKS);
402 error_handler()->OnSingleDataTypeUnrecoverableError(error);
403 return;
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)) {
415 return;
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!";
421 return;
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()) {
428 return;
431 BookmarkNodeChanged(model, node);
434 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
435 BookmarkModel* model, const BookmarkNode* node) {
436 if (!CanSyncNode(node))
437 return;
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(),
452 &sync_child)) {
453 syncer::SyncError error(FROM_HERE,
454 syncer::SyncError::DATATYPE_ERROR,
455 "Failed to init sync node from chrome node",
456 syncer::BOOKMARKS);
457 error_handler()->OnSingleDataTypeUnrecoverableError(error);
458 return;
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",
468 syncer::BOOKMARKS);
469 error_handler()->OnSingleDataTypeUnrecoverableError(error);
470 return;
475 // TODO(haitaol): Filter out children that didn't actually change.
476 UpdateTransactionVersion(new_version, model, children);
479 // static
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";
486 return false;
489 bool success = false;
490 if (index == 0) {
491 // Insert into first position.
492 success = (operation == CREATE) ?
493 dst->InitBookmarkByCreation(sync_parent, NULL) :
494 dst->SetPosition(sync_parent, NULL);
495 if (success) {
496 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
497 DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
498 DCHECK_EQ(dst->GetPredecessorId(), syncer::kInvalidId);
500 } else {
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";
506 return false;
508 success = (operation == CREATE) ?
509 dst->InitBookmarkByCreation(sync_parent, &sync_prev) :
510 dst->SetPosition(sync_parent, &sync_prev);
511 if (success) {
512 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
513 DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
514 DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
517 return success;
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
522 // model.
523 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
524 const syncer::BaseTransaction* trans,
525 int64 model_version,
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
548 // changes.
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;
568 ++it) {
569 const BookmarkNode* dst =
570 model_associator_->GetChromeNodeFromSyncId(it->id);
572 // Ignore changes to the permanent top-level nodes. We only care about
573 // their children.
574 if (model->is_permanent_node(dst))
575 continue;
577 // Can't do anything if we can't find the chrome node.
578 if (!dst)
579 continue;
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.
583 if (!dst->empty()) {
584 if (!foster_parent) {
585 foster_parent = model->AddFolder(model->other_node(),
586 model->other_node()->child_count(),
587 base::string16());
588 if (!foster_parent) {
589 syncer::SyncError error(FROM_HERE,
590 syncer::SyncError::DATATYPE_ERROR,
591 "Failed to create foster parent",
592 syncer::BOOKMARKS);
593 error_handler()->OnSingleDataTypeUnrecoverableError(error);
594 return;
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);
608 if (index > -1)
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
628 // their children.
629 if (model->is_permanent_node(dst))
630 continue;
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);
642 continue;
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",
653 syncer::BOOKMARKS);
654 error_handler()->OnSingleDataTypeUnrecoverableError(error);
655 return;
658 const BookmarkNode* parent =
659 model_associator_->GetChromeNodeFromSyncId(src.GetParentId());
660 if (!parent) {
661 LOG(ERROR) << "Could not find parent of node being added/updated."
662 << " Node title: " << src.GetTitle()
663 << ", parent id = " << src.GetParentId();
664 continue;
667 if (dst) {
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());
674 } else {
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,
679 parent,
680 model,
681 profile_,
682 parent->child_count());
683 if (!dst) {
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();
689 continue;
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.
708 if (foster_parent) {
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);
733 // Static.
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,
739 Profile* profile) {
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()) {
747 model->SetDateAdded(
748 node,
749 base::Time::FromInternalValue(specifics.creation_time_us()));
751 SetBookmarkFavicon(&sync_node, node, model, profile);
752 model->SetNodeMetaInfoMap(node, *GetBookmarkMetaInfo(&sync_node));
755 // static
756 void BookmarkChangeProcessor::UpdateTransactionVersion(
757 int64 new_version,
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);
768 // static
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,
775 Profile* profile,
776 int index) {
777 DCHECK(parent);
779 const BookmarkNode* node;
780 if (sync_node->GetIsFolder()) {
781 node =
782 model->AddFolderWithMetaInfo(parent,
783 index,
784 base::UTF8ToUTF16(sync_node->GetTitle()),
785 GetBookmarkMetaInfo(sync_node).get());
786 } else {
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(
794 parent,
795 index,
796 base::UTF8ToUTF16(sync_node->GetTitle()),
797 GURL(specifics.url()),
798 create_time,
799 GetBookmarkMetaInfo(sync_node).get());
800 if (node)
801 SetBookmarkFavicon(sync_node, node, model, profile);
804 return node;
807 // static
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,
813 Profile* profile) {
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())
818 return false;
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
827 // unique.
828 if (icon_url.is_empty())
829 icon_url = bookmark_node->url();
831 ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
833 return true;
836 // static
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();
854 // static
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) {
867 size_t index = 0;
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
874 // different.
875 break;
878 if (index == size) {
879 // The original meta info from the sync model is already equivalent to
880 // |meta_info_map|.
881 return;
885 // Clear and reset meta info in bookmark specifics.
886 specifics.clear_meta_info();
887 if (meta_info_map) {
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);
899 // static
900 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
901 const BookmarkNode* bookmark_node,
902 Profile* profile,
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
916 // as such.
917 gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
918 favicon_service->MergeFavicon(bookmark_node->url(),
919 icon_url,
920 favicon_base::FAVICON,
921 bitmap_data,
922 pixel_size);
925 // static
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