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