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