Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / sync / glue / bookmark_change_processor.cc
blob1ecef95836fe7744c371202a7633a00fd99e176b
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.h"
18 #include "chrome/browser/favicon/favicon_service_factory.h"
19 #include "chrome/browser/history/history_service.h"
20 #include "chrome/browser/history/history_service_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/sync/profile_sync_service.h"
23 #include "chrome/browser/undo/bookmark_undo_service.h"
24 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
25 #include "chrome/browser/undo/bookmark_undo_utils.h"
26 #include "components/bookmarks/core/browser/bookmark_model.h"
27 #include "components/bookmarks/core/browser/bookmark_utils.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "sync/internal_api/public/change_record.h"
30 #include "sync/internal_api/public/read_node.h"
31 #include "sync/internal_api/public/write_node.h"
32 #include "sync/internal_api/public/write_transaction.h"
33 #include "sync/syncable/entry.h" // TODO(tim): Investigating bug 121587.
34 #include "sync/syncable/syncable_write_transaction.h"
35 #include "ui/gfx/favicon_size.h"
36 #include "ui/gfx/image/image_util.h"
38 using content::BrowserThread;
39 using syncer::ChangeRecord;
40 using syncer::ChangeRecordList;
42 namespace browser_sync {
44 static const char kMobileBookmarksTag[] = "synced_bookmarks";
46 BookmarkChangeProcessor::BookmarkChangeProcessor(
47 Profile* profile,
48 BookmarkModelAssociator* model_associator,
49 DataTypeErrorHandler* error_handler)
50 : ChangeProcessor(error_handler),
51 bookmark_model_(NULL),
52 profile_(profile),
53 model_associator_(model_associator) {
54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
55 DCHECK(model_associator);
56 DCHECK(profile);
57 DCHECK(error_handler);
60 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
61 if (bookmark_model_)
62 bookmark_model_->RemoveObserver(this);
65 void BookmarkChangeProcessor::StartImpl() {
66 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
67 DCHECK(!bookmark_model_);
68 bookmark_model_ = BookmarkModelFactory::GetForProfile(profile_);
69 DCHECK(bookmark_model_->loaded());
70 bookmark_model_->AddObserver(this);
73 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
74 const BookmarkNode* src,
75 BookmarkModel* model,
76 syncer::WriteNode* dst) {
77 // Set the properties of the item.
78 dst->SetIsFolder(src->is_folder());
79 dst->SetTitle(base::UTF16ToUTF8(src->GetTitle()));
80 sync_pb::BookmarkSpecifics bookmark_specifics(dst->GetBookmarkSpecifics());
81 if (!src->is_folder())
82 bookmark_specifics.set_url(src->url().spec());
83 bookmark_specifics.set_creation_time_us(src->date_added().ToInternalValue());
84 dst->SetBookmarkSpecifics(bookmark_specifics);
85 SetSyncNodeFavicon(src, model, dst);
86 SetSyncNodeMetaInfo(src, dst);
89 // static
90 void BookmarkChangeProcessor::EncodeFavicon(
91 const BookmarkNode* src,
92 BookmarkModel* model,
93 scoped_refptr<base::RefCountedMemory>* dst) {
94 const gfx::Image& favicon = model->GetFavicon(src);
96 // Check for empty images. This can happen if the favicon is
97 // still being loaded.
98 if (favicon.IsEmpty())
99 return;
101 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
102 // sync subsystem.
103 *dst = favicon.As1xPNGBytes();
106 void BookmarkChangeProcessor::RemoveOneSyncNode(syncer::WriteNode* sync_node) {
107 // This node should have no children.
108 DCHECK(!sync_node->HasChildren());
109 // Remove association and delete the sync node.
110 model_associator_->Disassociate(sync_node->GetId());
111 sync_node->Tombstone();
114 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
115 const BookmarkNode* topmost) {
116 int64 new_version =
117 syncer::syncable::kInvalidTransactionVersion;
119 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
120 syncer::WriteNode topmost_sync_node(&trans);
121 if (!model_associator_->InitSyncNodeFromChromeId(topmost->id(),
122 &topmost_sync_node)) {
123 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
124 std::string());
125 return;
127 // Check that |topmost| has been unlinked.
128 DCHECK(topmost->is_root());
129 RemoveAllChildNodes(&trans, topmost->id());
130 // Remove the node itself.
131 RemoveOneSyncNode(&topmost_sync_node);
134 // Don't need to update versions of deleted nodes.
135 UpdateTransactionVersion(new_version, bookmark_model_,
136 std::vector<const BookmarkNode*>());
139 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
140 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
142 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
144 RemoveAllChildNodes(&trans, bookmark_model_->bookmark_bar_node()->id());
145 RemoveAllChildNodes(&trans, bookmark_model_->other_node()->id());
146 // Remove mobile bookmarks node only if it is present.
147 const int64 mobile_bookmark_id = bookmark_model_->mobile_node()->id();
148 if (model_associator_->GetSyncIdFromChromeId(mobile_bookmark_id) !=
149 syncer::kInvalidId) {
150 RemoveAllChildNodes(&trans, bookmark_model_->mobile_node()->id());
154 // Don't need to update versions of deleted nodes.
155 UpdateTransactionVersion(new_version, bookmark_model_,
156 std::vector<const BookmarkNode*>());
159 void BookmarkChangeProcessor::RemoveAllChildNodes(
160 syncer::WriteTransaction* trans, const int64& topmost_node_id) {
161 syncer::WriteNode topmost_node(trans);
162 if (!model_associator_->InitSyncNodeFromChromeId(topmost_node_id,
163 &topmost_node)) {
164 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
165 std::string());
166 return;
168 const int64 topmost_sync_id = topmost_node.GetId();
170 // Do a DFS and delete all the child sync nodes, use sync id instead of
171 // bookmark node ids since the bookmark nodes may already be deleted.
172 // The equivalent recursive version of this iterative DFS:
173 // remove_all_children(node_id, topmost_node_id):
174 // node.initByIdLookup(node_id)
175 // while(node.GetFirstChildId() != syncer::kInvalidId)
176 // remove_all_children(node.GetFirstChildId(), topmost_node_id)
177 // if(node_id != topmost_node_id)
178 // delete node
180 std::stack<int64> dfs_sync_id_stack;
181 // Push the topmost node.
182 dfs_sync_id_stack.push(topmost_sync_id);
183 while (!dfs_sync_id_stack.empty()) {
184 const int64 sync_node_id = dfs_sync_id_stack.top();
185 syncer::WriteNode node(trans);
186 node.InitByIdLookup(sync_node_id);
187 if (!node.GetIsFolder() || node.GetFirstChildId() == syncer::kInvalidId) {
188 // All children of the node has been processed, delete the node and
189 // pop it off the stack.
190 dfs_sync_id_stack.pop();
191 // Do not delete the topmost node.
192 if (sync_node_id != topmost_sync_id) {
193 RemoveOneSyncNode(&node);
194 } else {
195 // if we are processing topmost node, all other nodes must be processed
196 // the stack should be empty.
197 DCHECK(dfs_sync_id_stack.empty());
199 } else {
200 int64 child_id = node.GetFirstChildId();
201 if (child_id != syncer::kInvalidId) {
202 dfs_sync_id_stack.push(child_id);
208 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
209 bool ids_reassigned) {
210 NOTREACHED();
213 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
214 BookmarkModel* model) {
215 NOTREACHED();
216 bookmark_model_ = NULL;
219 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
220 const BookmarkNode* parent,
221 int index) {
222 DCHECK(share_handle());
224 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
225 int64 sync_id = syncer::kInvalidId;
227 // Acquire a scoped write lock via a transaction.
228 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
229 sync_id = CreateSyncNode(parent, model, index, &trans,
230 model_associator_, error_handler());
233 if (syncer::kInvalidId != sync_id) {
234 // Siblings of added node in sync DB will also be updated to reflect new
235 // PREV_ID/NEXT_ID and thus get a new version. But we only update version
236 // of added node here. After switching to ordinals for positioning,
237 // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
238 UpdateTransactionVersion(
239 new_version, model,
240 std::vector<const BookmarkNode*>(1, parent->GetChild(index)));
244 // static
245 int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent,
246 BookmarkModel* model, int index, syncer::WriteTransaction* trans,
247 BookmarkModelAssociator* associator,
248 DataTypeErrorHandler* error_handler) {
249 const BookmarkNode* child = parent->GetChild(index);
250 DCHECK(child);
252 // Create a WriteNode container to hold the new node.
253 syncer::WriteNode sync_child(trans);
255 // Actually create the node with the appropriate initial position.
256 if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) {
257 error_handler->OnSingleDatatypeUnrecoverableError(FROM_HERE,
258 "Sync node creation failed; recovery unlikely");
259 return syncer::kInvalidId;
262 UpdateSyncNodeProperties(child, model, &sync_child);
264 // Associate the ID from the sync domain with the bookmark node, so that we
265 // can refer back to this item later.
266 associator->Associate(child, sync_child.GetId());
268 return sync_child.GetId();
271 void BookmarkChangeProcessor::BookmarkNodeRemoved(
272 BookmarkModel* model,
273 const BookmarkNode* parent,
274 int index,
275 const BookmarkNode* node,
276 const std::set<GURL>& removed_urls) {
277 RemoveSyncNodeHierarchy(node);
280 void BookmarkChangeProcessor::BookmarkAllNodesRemoved(
281 BookmarkModel* model,
282 const std::set<GURL>& removed_urls) {
283 RemoveAllSyncNodes();
286 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model,
287 const BookmarkNode* node) {
288 // We shouldn't see changes to the top-level nodes.
289 if (model->is_permanent_node(node)) {
290 NOTREACHED() << "Saw update to permanent node!";
291 return;
294 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
296 // Acquire a scoped write lock via a transaction.
297 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
299 // Lookup the sync node that's associated with |node|.
300 syncer::WriteNode sync_node(&trans);
301 if (!model_associator_->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
302 // TODO(tim): Investigating bug 121587.
303 if (model_associator_->GetSyncIdFromChromeId(node->id()) ==
304 syncer::kInvalidId) {
305 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
306 "Bookmark id not found in model associator on BookmarkNodeChanged");
307 LOG(ERROR) << "Bad id.";
308 } else if (!sync_node.GetEntry()->good()) {
309 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
310 "Could not InitByIdLookup on BookmarkNodeChanged, good() failed");
311 LOG(ERROR) << "Bad entry.";
312 } else if (sync_node.GetEntry()->GetIsDel()) {
313 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
314 "Could not InitByIdLookup on BookmarkNodeChanged, is_del true");
315 LOG(ERROR) << "Deleted entry.";
316 } else {
317 syncer::Cryptographer* crypto = trans.GetCryptographer();
318 syncer::ModelTypeSet encrypted_types(trans.GetEncryptedTypes());
319 const sync_pb::EntitySpecifics& specifics =
320 sync_node.GetEntry()->GetSpecifics();
321 CHECK(specifics.has_encrypted());
322 const bool can_decrypt = crypto->CanDecrypt(specifics.encrypted());
323 const bool agreement = encrypted_types.Has(syncer::BOOKMARKS);
324 if (!agreement && !can_decrypt) {
325 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
326 "Could not InitByIdLookup on BookmarkNodeChanged, "
327 " Cryptographer thinks bookmarks not encrypted, and CanDecrypt"
328 " failed.");
329 LOG(ERROR) << "Case 1.";
330 } else if (agreement && can_decrypt) {
331 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
332 "Could not InitByIdLookup on BookmarkNodeChanged, "
333 " Cryptographer thinks bookmarks are encrypted, and CanDecrypt"
334 " succeeded (?!), but DecryptIfNecessary failed.");
335 LOG(ERROR) << "Case 2.";
336 } else if (agreement) {
337 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
338 "Could not InitByIdLookup on BookmarkNodeChanged, "
339 " Cryptographer thinks bookmarks are encrypted, but CanDecrypt"
340 " failed.");
341 LOG(ERROR) << "Case 3.";
342 } else {
343 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
344 "Could not InitByIdLookup on BookmarkNodeChanged, "
345 " Cryptographer thinks bookmarks not encrypted, but CanDecrypt"
346 " succeeded (super weird, btw)");
347 LOG(ERROR) << "Case 4.";
350 return;
353 UpdateSyncNodeProperties(node, model, &sync_node);
355 DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
356 DCHECK_EQ(model_associator_->GetChromeNodeFromSyncId(
357 sync_node.GetParentId()),
358 node->parent());
359 DCHECK_EQ(node->parent()->GetIndexOf(node), sync_node.GetPositionIndex());
362 UpdateTransactionVersion(new_version, model,
363 std::vector<const BookmarkNode*>(1, node));
366 void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
367 BookmarkModel* model, const BookmarkNode* node) {
368 BookmarkNodeChanged(model, node);
371 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model,
372 const BookmarkNode* old_parent, int old_index,
373 const BookmarkNode* new_parent, int new_index) {
374 const BookmarkNode* child = new_parent->GetChild(new_index);
375 // We shouldn't see changes to the top-level nodes.
376 if (model->is_permanent_node(child)) {
377 NOTREACHED() << "Saw update to permanent node!";
378 return;
381 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
383 // Acquire a scoped write lock via a transaction.
384 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
386 // Lookup the sync node that's associated with |child|.
387 syncer::WriteNode sync_node(&trans);
388 if (!model_associator_->InitSyncNodeFromChromeId(child->id(), &sync_node)) {
389 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
390 std::string());
391 return;
394 if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node,
395 model_associator_)) {
396 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
397 std::string());
398 return;
402 UpdateTransactionVersion(new_version, model,
403 std::vector<const BookmarkNode*>(1, child));
406 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
407 BookmarkModel* model,
408 const BookmarkNode* node) {
409 BookmarkNodeChanged(model, node);
412 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
413 BookmarkModel* model, const BookmarkNode* node) {
414 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
415 std::vector<const BookmarkNode*> children;
417 // Acquire a scoped write lock via a transaction.
418 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
420 // The given node's children got reordered. We need to reorder all the
421 // children of the corresponding sync node.
422 for (int i = 0; i < node->child_count(); ++i) {
423 const BookmarkNode* child = node->GetChild(i);
424 children.push_back(child);
426 syncer::WriteNode sync_child(&trans);
427 if (!model_associator_->InitSyncNodeFromChromeId(child->id(),
428 &sync_child)) {
429 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
430 std::string());
431 return;
433 DCHECK_EQ(sync_child.GetParentId(),
434 model_associator_->GetSyncIdFromChromeId(node->id()));
436 if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child,
437 model_associator_)) {
438 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
439 std::string());
440 return;
445 // TODO(haitaol): Filter out children that didn't actually change.
446 UpdateTransactionVersion(new_version, model, children);
449 // static
450 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation,
451 const BookmarkNode* parent, int index, syncer::WriteTransaction* trans,
452 syncer::WriteNode* dst, BookmarkModelAssociator* associator) {
453 syncer::ReadNode sync_parent(trans);
454 if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) {
455 LOG(WARNING) << "Parent lookup failed";
456 return false;
459 bool success = false;
460 if (index == 0) {
461 // Insert into first position.
462 success = (operation == CREATE) ?
463 dst->InitBookmarkByCreation(sync_parent, NULL) :
464 dst->SetPosition(sync_parent, NULL);
465 if (success) {
466 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
467 DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
468 DCHECK_EQ(dst->GetPredecessorId(), syncer::kInvalidId);
470 } else {
471 // Find the bookmark model predecessor, and insert after it.
472 const BookmarkNode* prev = parent->GetChild(index - 1);
473 syncer::ReadNode sync_prev(trans);
474 if (!associator->InitSyncNodeFromChromeId(prev->id(), &sync_prev)) {
475 LOG(WARNING) << "Predecessor lookup failed";
476 return false;
478 success = (operation == CREATE) ?
479 dst->InitBookmarkByCreation(sync_parent, &sync_prev) :
480 dst->SetPosition(sync_parent, &sync_prev);
481 if (success) {
482 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
483 DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
484 DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
487 return success;
490 // ApplyModelChanges is called by the sync backend after changes have been made
491 // to the sync engine's model. Apply these changes to the browser bookmark
492 // model.
493 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
494 const syncer::BaseTransaction* trans,
495 int64 model_version,
496 const syncer::ImmutableChangeRecordList& changes) {
497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
498 // A note about ordering. Sync backend is responsible for ordering the change
499 // records in the following order:
501 // 1. Deletions, from leaves up to parents.
502 // 2. Existing items with synced parents & predecessors.
503 // 3. New items with synced parents & predecessors.
504 // 4. Items with parents & predecessors in the list.
505 // 5. Repeat #4 until all items are in the list.
507 // "Predecessor" here means the previous item within a given folder; an item
508 // in the first position is always said to have a synced predecessor.
509 // For the most part, applying these changes in the order given will yield
510 // the correct result. There is one exception, however: for items that are
511 // moved away from a folder that is being deleted, we will process the delete
512 // before the move. Since deletions in the bookmark model propagate from
513 // parent to child, we must move them to a temporary location.
514 BookmarkModel* model = bookmark_model_;
516 // We are going to make changes to the bookmarks model, but don't want to end
517 // up in a feedback loop, so remove ourselves as an observer while applying
518 // changes.
519 model->RemoveObserver(this);
521 // Changes made to the bookmark model due to sync should not be undoable.
522 #if !defined(OS_ANDROID)
523 ScopedSuspendBookmarkUndo suspend_undo(profile_);
524 #endif
526 // Notify UI intensive observers of BookmarkModel that we are about to make
527 // potentially significant changes to it, so the updates may be batched. For
528 // example, on Mac, the bookmarks bar displays animations when bookmark items
529 // are added or deleted.
530 model->BeginExtensiveChanges();
532 // A parent to hold nodes temporarily orphaned by parent deletion. It is
533 // created only if it is needed.
534 const BookmarkNode* foster_parent = NULL;
536 // Iterate over the deletions, which are always at the front of the list.
537 ChangeRecordList::const_iterator it;
538 for (it = changes.Get().begin();
539 it != changes.Get().end() && it->action == ChangeRecord::ACTION_DELETE;
540 ++it) {
541 const BookmarkNode* dst =
542 model_associator_->GetChromeNodeFromSyncId(it->id);
544 // Ignore changes to the permanent top-level nodes. We only care about
545 // their children.
546 if (model->is_permanent_node(dst))
547 continue;
549 // Can't do anything if we can't find the chrome node.
550 if (!dst)
551 continue;
553 // Children of a deleted node should not be deleted; they may be
554 // reparented by a later change record. Move them to a temporary place.
555 if (!dst->empty()) {
556 if (!foster_parent) {
557 foster_parent = model->AddFolder(model->other_node(),
558 model->other_node()->child_count(),
559 base::string16());
560 if (!foster_parent) {
561 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
562 "Failed to create foster parent.");
563 return;
566 for (int i = dst->child_count() - 1; i >= 0; --i) {
567 model->Move(dst->GetChild(i), foster_parent,
568 foster_parent->child_count());
571 DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
573 model_associator_->Disassociate(it->id);
575 const BookmarkNode* parent = dst->parent();
576 int index = parent->GetIndexOf(dst);
577 if (index > -1)
578 model->Remove(parent, index);
581 // A map to keep track of some reordering work we defer until later.
582 std::multimap<int, const BookmarkNode*> to_reposition;
584 syncer::ReadNode synced_bookmarks(trans);
585 int64 synced_bookmarks_id = syncer::kInvalidId;
586 if (synced_bookmarks.InitByTagLookup(kMobileBookmarksTag) ==
587 syncer::BaseNode::INIT_OK) {
588 synced_bookmarks_id = synced_bookmarks.GetId();
591 // Continue iterating where the previous loop left off.
592 for ( ; it != changes.Get().end(); ++it) {
593 const BookmarkNode* dst =
594 model_associator_->GetChromeNodeFromSyncId(it->id);
596 // Ignore changes to the permanent top-level nodes. We only care about
597 // their children.
598 if (model->is_permanent_node(dst))
599 continue;
601 // Because the Synced Bookmarks node can be created server side, it's
602 // possible it'll arrive at the client as an update. In that case it won't
603 // have been associated at startup, the GetChromeNodeFromSyncId call above
604 // will return NULL, and we won't detect it as a permanent node, resulting
605 // in us trying to create it here (which will fail). Therefore, we add
606 // special logic here just to detect the Synced Bookmarks folder.
607 if (synced_bookmarks_id != syncer::kInvalidId &&
608 it->id == synced_bookmarks_id) {
609 // This is a newly created Synced Bookmarks node. Associate it.
610 model_associator_->Associate(model->mobile_node(), it->id);
611 continue;
614 DCHECK_NE(it->action, ChangeRecord::ACTION_DELETE)
615 << "We should have passed all deletes by this point.";
617 syncer::ReadNode src(trans);
618 if (src.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) {
619 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
620 "ApplyModelChanges was passed a bad ID");
621 return;
624 const BookmarkNode* parent =
625 model_associator_->GetChromeNodeFromSyncId(src.GetParentId());
626 if (!parent) {
627 LOG(ERROR) << "Could not find parent of node being added/updated."
628 << " Node title: " << src.GetTitle()
629 << ", parent id = " << src.GetParentId();
630 continue;
633 if (dst) {
634 DCHECK(it->action == ChangeRecord::ACTION_UPDATE)
635 << "ACTION_UPDATE should be seen if and only if the node is known.";
636 UpdateBookmarkWithSyncData(src, model, dst, profile_);
638 // Move all modified entries to the right. We'll fix it later.
639 model->Move(dst, parent, parent->child_count());
640 } else {
641 DCHECK(it->action == ChangeRecord::ACTION_ADD)
642 << "ACTION_ADD should be seen if and only if the node is unknown.";
644 dst = CreateBookmarkNode(&src,
645 parent,
646 model,
647 profile_,
648 parent->child_count());
649 if (!dst) {
650 // We ignore bookmarks we can't add. Chances are this is caused by
651 // a bookmark that was not fully associated.
652 LOG(ERROR) << "Failed to create bookmark node with title "
653 << src.GetTitle() + " and url "
654 << src.GetBookmarkSpecifics().url();
655 continue;
657 model_associator_->Associate(dst, src.GetId());
660 to_reposition.insert(std::make_pair(src.GetPositionIndex(), dst));
661 bookmark_model_->SetNodeSyncTransactionVersion(dst, model_version);
664 // When we added or updated bookmarks in the previous loop, we placed them to
665 // the far right position. Now we iterate over all these modified items in
666 // sync order, left to right, moving them into their proper positions.
667 for (std::multimap<int, const BookmarkNode*>::iterator it =
668 to_reposition.begin(); it != to_reposition.end(); ++it) {
669 const BookmarkNode* parent = it->second->parent();
670 model->Move(it->second, parent, it->first);
673 // Clean up the temporary node.
674 if (foster_parent) {
675 // There should be no nodes left under the foster parent.
676 DCHECK_EQ(foster_parent->child_count(), 0);
677 model->Remove(foster_parent->parent(),
678 foster_parent->parent()->GetIndexOf(foster_parent));
679 foster_parent = NULL;
682 // The visibility of the mobile node may need to change.
683 model_associator_->UpdatePermanentNodeVisibility();
685 // Notify UI intensive observers of BookmarkModel that all updates have been
686 // applied, and that they may now be consumed. This prevents issues like the
687 // one described in crbug.com/281562, where old and new items on the bookmarks
688 // bar would overlap.
689 model->EndExtensiveChanges();
691 // We are now ready to hear about bookmarks changes again.
692 model->AddObserver(this);
694 // All changes are applied in bookmark model. Set transaction version on
695 // bookmark model to mark as synced.
696 model->SetNodeSyncTransactionVersion(model->root_node(), model_version);
699 // Static.
700 // Update a bookmark node with specified sync data.
701 void BookmarkChangeProcessor::UpdateBookmarkWithSyncData(
702 const syncer::BaseNode& sync_node,
703 BookmarkModel* model,
704 const BookmarkNode* node,
705 Profile* profile) {
706 DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
707 const sync_pb::BookmarkSpecifics& specifics =
708 sync_node.GetBookmarkSpecifics();
709 if (!sync_node.GetIsFolder())
710 model->SetURL(node, GURL(specifics.url()));
711 model->SetTitle(node, base::UTF8ToUTF16(sync_node.GetTitle()));
712 if (specifics.has_creation_time_us()) {
713 model->SetDateAdded(
714 node,
715 base::Time::FromInternalValue(specifics.creation_time_us()));
717 SetBookmarkFavicon(&sync_node, node, model, profile);
718 model->SetNodeMetaInfoMap(node, *GetBookmarkMetaInfo(&sync_node));
721 // static
722 void BookmarkChangeProcessor::UpdateTransactionVersion(
723 int64 new_version,
724 BookmarkModel* model,
725 const std::vector<const BookmarkNode*>& nodes) {
726 if (new_version != syncer::syncable::kInvalidTransactionVersion) {
727 model->SetNodeSyncTransactionVersion(model->root_node(), new_version);
728 for (size_t i = 0; i < nodes.size(); ++i) {
729 model->SetNodeSyncTransactionVersion(nodes[i], new_version);
734 // static
735 // Creates a bookmark node under the given parent node from the given sync
736 // node. Returns the newly created node.
737 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
738 syncer::BaseNode* sync_node,
739 const BookmarkNode* parent,
740 BookmarkModel* model,
741 Profile* profile,
742 int index) {
743 DCHECK(parent);
745 const BookmarkNode* node;
746 if (sync_node->GetIsFolder()) {
747 node =
748 model->AddFolderWithMetaInfo(parent,
749 index,
750 base::UTF8ToUTF16(sync_node->GetTitle()),
751 GetBookmarkMetaInfo(sync_node).get());
752 } else {
753 // 'creation_time_us' was added in m24. Assume a time of 0 means now.
754 const sync_pb::BookmarkSpecifics& specifics =
755 sync_node->GetBookmarkSpecifics();
756 const int64 create_time_internal = specifics.creation_time_us();
757 base::Time create_time = (create_time_internal == 0) ?
758 base::Time::Now() : base::Time::FromInternalValue(create_time_internal);
759 node = model->AddURLWithCreationTimeAndMetaInfo(
760 parent,
761 index,
762 base::UTF8ToUTF16(sync_node->GetTitle()),
763 GURL(specifics.url()),
764 create_time,
765 GetBookmarkMetaInfo(sync_node).get());
766 if (node)
767 SetBookmarkFavicon(sync_node, node, model, profile);
770 return node;
773 // static
774 // Sets the favicon of the given bookmark node from the given sync node.
775 bool BookmarkChangeProcessor::SetBookmarkFavicon(
776 const syncer::BaseNode* sync_node,
777 const BookmarkNode* bookmark_node,
778 BookmarkModel* bookmark_model,
779 Profile* profile) {
780 const sync_pb::BookmarkSpecifics& specifics =
781 sync_node->GetBookmarkSpecifics();
782 const std::string& icon_bytes_str = specifics.favicon();
783 if (icon_bytes_str.empty())
784 return false;
786 scoped_refptr<base::RefCountedString> icon_bytes(
787 new base::RefCountedString());
788 icon_bytes->data().assign(icon_bytes_str);
789 GURL icon_url(specifics.icon_url());
791 // Old clients may not be syncing the favicon URL. If the icon URL is not
792 // synced, use the page URL as a fake icon URL as it is guaranteed to be
793 // unique.
794 if (icon_url.is_empty())
795 icon_url = bookmark_node->url();
797 ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
799 return true;
802 // static
803 scoped_ptr<BookmarkNode::MetaInfoMap>
804 BookmarkChangeProcessor::GetBookmarkMetaInfo(
805 const syncer::BaseNode* sync_node) {
806 const sync_pb::BookmarkSpecifics& specifics =
807 sync_node->GetBookmarkSpecifics();
808 scoped_ptr<BookmarkNode::MetaInfoMap> meta_info_map(
809 new BookmarkNode::MetaInfoMap);
810 for (int i = 0; i < specifics.meta_info_size(); ++i) {
811 (*meta_info_map)[specifics.meta_info(i).key()] =
812 specifics.meta_info(i).value();
814 return meta_info_map.Pass();
817 // static
818 void BookmarkChangeProcessor::SetSyncNodeMetaInfo(
819 const BookmarkNode* node,
820 syncer::WriteNode* sync_node) {
821 sync_pb::BookmarkSpecifics specifics = sync_node->GetBookmarkSpecifics();
822 specifics.clear_meta_info();
823 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
824 if (meta_info_map) {
825 for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map->begin();
826 it != meta_info_map->end(); ++it) {
827 sync_pb::MetaInfo* meta_info = specifics.add_meta_info();
828 meta_info->set_key(it->first);
829 meta_info->set_value(it->second);
832 sync_node->SetBookmarkSpecifics(specifics);
835 // static
836 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
837 const BookmarkNode* bookmark_node,
838 Profile* profile,
839 const GURL& icon_url,
840 const scoped_refptr<base::RefCountedMemory>& bitmap_data) {
841 HistoryService* history =
842 HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
843 FaviconService* favicon_service =
844 FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
846 history->AddPageNoVisitForBookmark(bookmark_node->url(),
847 bookmark_node->GetTitle());
848 // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
849 // overwrite the cached 2x favicon bitmap. Sync favicons are always
850 // gfx::kFaviconSize in width and height. Store the favicon into history
851 // as such.
852 gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
853 favicon_service->MergeFavicon(bookmark_node->url(),
854 icon_url,
855 favicon_base::FAVICON,
856 bitmap_data,
857 pixel_size);
860 // static
861 void BookmarkChangeProcessor::SetSyncNodeFavicon(
862 const BookmarkNode* bookmark_node,
863 BookmarkModel* model,
864 syncer::WriteNode* sync_node) {
865 scoped_refptr<base::RefCountedMemory> favicon_bytes(NULL);
866 EncodeFavicon(bookmark_node, model, &favicon_bytes);
867 if (favicon_bytes.get() && favicon_bytes->size()) {
868 sync_pb::BookmarkSpecifics updated_specifics(
869 sync_node->GetBookmarkSpecifics());
870 updated_specifics.set_favicon(favicon_bytes->front(),
871 favicon_bytes->size());
872 updated_specifics.set_icon_url(bookmark_node->icon_url().spec());
873 sync_node->SetBookmarkSpecifics(updated_specifics);
877 } // namespace browser_sync