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
39using content::BrowserThread;
40using syncer::ChangeRecord;
41using syncer::ChangeRecordList;
42
43namespace browser_sync {
44
45static const char kMobileBookmarksTag[] = "synced_bookmarks";
46
47BookmarkChangeProcessor::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
61BookmarkChangeProcessor::~BookmarkChangeProcessor() {
62  if (bookmark_model_)
63    bookmark_model_->RemoveObserver(this);
64}
65
66void 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
74void 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
91void 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
107void 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
115void 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
143void 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
165void 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
217void 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
257void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
258                                                  bool ids_reassigned) {
259  NOTREACHED();
260}
261
262void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) {
263  NOTREACHED();
264  bookmark_model_ = NULL;
265}
266
267void 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
277int64 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
306void 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
316void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved(
317    BookmarkModel* model,
318    const std::set<GURL>& removed_urls) {
319  RemoveAllSyncNodes();
320}
321
322void 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.
335int64 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
359void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
360    BookmarkModel* model, const BookmarkNode* node) {
361  BookmarkNodeChanged(model, node);
362}
363
364void 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
409void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
410    BookmarkModel* model,
411    const BookmarkNode* node) {
412  BookmarkNodeChanged(model, node);
413}
414
415void 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
461bool 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.
504void 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.
716void 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
737void 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.
752const 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.
790bool 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
818scoped_ptr<BookmarkNode::MetaInfoMap>
819BookmarkChangeProcessor::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
833void 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
851void 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
876void 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
892bool BookmarkChangeProcessor::CanSyncNode(const BookmarkNode* node) {
893  return bookmark_model_->client()->CanSyncNode(node);
894}
895
896}  // namespace browser_sync
897