Fix build break
[chromium-blink-merge.git] / chrome / browser / sync / glue / bookmark_change_processor.cc
blob0f22b2b2d2bbbb42e122efeb834a0916953b6382
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 <stack>
8 #include <vector>
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() {
55 if (bookmark_model_)
56 bookmark_model_->RemoveObserver(this);
59 void BookmarkChangeProcessor::StartImpl(Profile* profile) {
60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
61 DCHECK(profile);
62 profile_ = profile;
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,
71 BookmarkModel* model,
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);
84 // static
85 void BookmarkChangeProcessor::EncodeFavicon(
86 const BookmarkNode* src,
87 BookmarkModel* model,
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())
94 return;
96 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
97 // sync subsystem.
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) {
111 int64 new_version =
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,
119 std::string());
120 return;
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,
158 &topmost_node)) {
159 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
160 std::string());
161 return;
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)
173 // delete node
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);
189 } else {
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());
194 } else {
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) {
205 NOTREACHED();
208 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
209 BookmarkModel* model) {
210 NOTREACHED();
211 bookmark_model_ = NULL;
214 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
215 const BookmarkNode* parent,
216 int index) {
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(
234 new_version, model,
235 std::vector<const BookmarkNode*>(1, parent->GetChild(index)));
239 // static
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);
245 DCHECK(child);
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,
268 int index,
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!";
282 return;
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.";
307 } else {
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"
319 " failed.");
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"
331 " failed.");
332 LOG(ERROR) << "Case 3.";
333 } else {
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.";
341 return;
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()),
349 node->parent());
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(),
353 &sync_node,
354 model_associator_));
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!";
368 return;
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,
380 std::string());
381 return;
384 if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node,
385 model_associator_)) {
386 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
387 std::string());
388 return;
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(),
418 &sync_child)) {
419 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
420 std::string());
421 return;
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,
429 std::string());
430 return;
435 // TODO(haitaol): Filter out children that didn't actually change.
436 UpdateTransactionVersion(new_version, model, children);
439 // static
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";
446 return false;
449 bool success = false;
450 if (index == 0) {
451 // Insert into first position.
452 success = (operation == CREATE) ?
453 dst->InitBookmarkByCreation(sync_parent, NULL) :
454 dst->SetPosition(sync_parent, NULL);
455 if (success) {
456 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
457 DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
458 DCHECK_EQ(dst->GetPredecessorId(), syncer::kInvalidId);
460 } else {
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";
466 return false;
468 success = (operation == CREATE) ?
469 dst->InitBookmarkByCreation(sync_parent, &sync_prev) :
470 dst->SetPosition(sync_parent, &sync_prev);
471 if (success) {
472 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
473 DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
474 DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
477 return success;
480 // Static.
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) {
490 DCHECK(parent);
491 DCHECK(child_info);
492 int64 predecessor_id = child_info->GetPredecessorId();
493 // A return ID of kInvalidId indicates no predecessor.
494 if (predecessor_id == syncer::kInvalidId)
495 return 0;
497 // Otherwise, insert after the predecessor bookmark node.
498 const BookmarkNode* predecessor =
499 model_associator->GetChromeNodeFromSyncId(predecessor_id);
500 DCHECK(predecessor);
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
507 // model.
508 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
509 const syncer::BaseTransaction* trans,
510 int64 model_version,
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
533 // changes.
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
548 // their children.
549 if (model->is_permanent_node(dst))
550 continue;
551 if (it->action ==
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.
558 continue;
559 const BookmarkNode* parent = dst->parent();
560 if (!dst->empty()) {
561 if (!foster_parent) {
562 foster_parent = model->AddFolder(model->other_node(),
563 model->other_node()->child_count(),
564 string16());
565 if (!foster_parent) {
566 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
567 "Failed to create foster parent.");
568 return;
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);
579 if (index > -1)
580 model->Remove(parent, index);
581 dst = NULL;
582 } else {
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");
592 return;
595 const BookmarkNode* node = CreateOrUpdateBookmarkNode(&src, model,
596 profile_,
597 model_associator_);
598 if (node) {
599 bookmark_model_->SetNodeMetaInfo(node, kBookmarkTransactionVersionKey,
600 base::Int64ToString(model_version));
601 } else {
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);
615 } else {
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.
626 if (foster_parent) {
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));
646 // Static.
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,
652 Profile* profile,
653 BookmarkModelAssociator* model_associator) {
654 const BookmarkNode* parent =
655 model_associator->GetChromeNodeFromSyncId(src->GetParentId());
656 if (!parent) {
657 DLOG(WARNING) << "Could not find parent of node being added/updated."
658 << " Node title: " << src->GetTitle()
659 << ", parent id = " << src->GetParentId();
661 return NULL;
663 int index = CalculateBookmarkModelInsertionIndex(parent,
664 src,
665 model_associator);
666 const BookmarkNode* dst = model_associator->GetChromeNodeFromSyncId(
667 src->GetId());
668 if (!dst) {
669 dst = CreateBookmarkNode(src, parent, model, profile, index);
670 if (dst)
671 model_associator->Associate(dst, src->GetId());
672 } else {
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);
693 return dst;
696 // static
697 void BookmarkChangeProcessor::UpdateTransactionVersion(
698 int64 new_version,
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));
711 // static
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,
718 Profile* profile,
719 int index) {
720 DCHECK(parent);
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()));
727 } else {
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);
737 if (node)
738 SetBookmarkFavicon(sync_node, node, model, profile);
740 return node;
743 // static
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,
749 Profile* profile) {
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())
754 return false;
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
763 // unique.
764 if (icon_url.is_empty())
765 icon_url = bookmark_node->url();
767 ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
769 return true;
772 // static
773 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
774 const BookmarkNode* bookmark_node,
775 Profile* profile,
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
788 // as such.
789 gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
790 favicon_service->MergeFavicon(bookmark_node->url(),
791 icon_url,
792 history::FAVICON,
793 bitmap_data,
794 pixel_size);
797 // static
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