Home | History | Annotate | Download | only in glue
      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()->Get(syncer::syncable::IS_DEL)) {
    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()->Get(syncer::syncable::SPECIFICS);
    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   // A parent to hold nodes temporarily orphaned by parent deletion.  It is
    511   // created only if it is needed.
    512   const BookmarkNode* foster_parent = NULL;
    513 
    514   // Iterate over the deletions, which are always at the front of the list.
    515   ChangeRecordList::const_iterator it;
    516   for (it = changes.Get().begin();
    517        it != changes.Get().end() && it->action == ChangeRecord::ACTION_DELETE;
    518        ++it) {
    519     const BookmarkNode* dst =
    520         model_associator_->GetChromeNodeFromSyncId(it->id);
    521 
    522     // Ignore changes to the permanent top-level nodes.  We only care about
    523     // their children.
    524     if (model->is_permanent_node(dst))
    525       continue;
    526 
    527     // Can't do anything if we can't find the chrome node.
    528     if (!dst)
    529       continue;
    530 
    531     // Children of a deleted node should not be deleted; they may be
    532     // reparented by a later change record.  Move them to a temporary place.
    533     if (!dst->empty()) {
    534       if (!foster_parent) {
    535         foster_parent = model->AddFolder(model->other_node(),
    536                                          model->other_node()->child_count(),
    537                                          string16());
    538         if (!foster_parent) {
    539           error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
    540               "Failed to create foster parent.");
    541           return;
    542         }
    543       }
    544       for (int i = dst->child_count() - 1; i >= 0; --i) {
    545         model->Move(dst->GetChild(i), foster_parent,
    546                     foster_parent->child_count());
    547       }
    548     }
    549     DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
    550 
    551     model_associator_->Disassociate(it->id);
    552 
    553     const BookmarkNode* parent = dst->parent();
    554     int index = parent->GetIndexOf(dst);
    555     if (index > -1)
    556       model->Remove(parent, index);
    557   }
    558 
    559   // A map to keep track of some reordering work we defer until later.
    560   std::multimap<int, const BookmarkNode*> to_reposition;
    561 
    562   syncer::ReadNode synced_bookmarks(trans);
    563   int64 synced_bookmarks_id = syncer::kInvalidId;
    564   if (synced_bookmarks.InitByTagLookup(kMobileBookmarksTag) ==
    565       syncer::BaseNode::INIT_OK) {
    566     synced_bookmarks_id = synced_bookmarks.GetId();
    567   }
    568 
    569   // Continue iterating where the previous loop left off.
    570   for ( ; it != changes.Get().end(); ++it) {
    571     const BookmarkNode* dst =
    572         model_associator_->GetChromeNodeFromSyncId(it->id);
    573 
    574     // Ignore changes to the permanent top-level nodes.  We only care about
    575     // their children.
    576     if (model->is_permanent_node(dst))
    577       continue;
    578 
    579     // Because the Synced Bookmarks node can be created server side, it's
    580     // possible it'll arrive at the client as an update. In that case it won't
    581     // have been associated at startup, the GetChromeNodeFromSyncId call above
    582     // will return NULL, and we won't detect it as a permanent node, resulting
    583     // in us trying to create it here (which will fail). Therefore, we add
    584     // special logic here just to detect the Synced Bookmarks folder.
    585     if (synced_bookmarks_id != syncer::kInvalidId &&
    586         it->id == synced_bookmarks_id) {
    587       // This is a newly created Synced Bookmarks node. Associate it.
    588       model_associator_->Associate(model->mobile_node(), it->id);
    589       continue;
    590     }
    591 
    592     DCHECK_NE(it->action, ChangeRecord::ACTION_DELETE)
    593         << "We should have passed all deletes by this point.";
    594 
    595     syncer::ReadNode src(trans);
    596     if (src.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) {
    597       error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
    598           "ApplyModelChanges was passed a bad ID");
    599       return;
    600     }
    601 
    602     const BookmarkNode* parent =
    603         model_associator_->GetChromeNodeFromSyncId(src.GetParentId());
    604     if (!parent) {
    605       LOG(ERROR) << "Could not find parent of node being added/updated."
    606         << " Node title: " << src.GetTitle()
    607         << ", parent id = " << src.GetParentId();
    608       continue;
    609     }
    610 
    611     if (dst) {
    612       DCHECK(it->action == ChangeRecord::ACTION_UPDATE)
    613           << "ACTION_UPDATE should be seen if and only if the node is known.";
    614       UpdateBookmarkWithSyncData(src, model, dst, profile_);
    615 
    616       // Move all modified entries to the right.  We'll fix it later.
    617       model->Move(dst, parent, parent->child_count());
    618     } else {
    619       DCHECK(it->action == ChangeRecord::ACTION_ADD)
    620           << "ACTION_ADD should be seen if and only if the node is unknown.";
    621 
    622       dst = CreateBookmarkNode(&src,
    623                                parent,
    624                                model,
    625                                profile_,
    626                                parent->child_count());
    627       if (!dst) {
    628         // We ignore bookmarks we can't add. Chances are this is caused by
    629         // a bookmark that was not fully associated.
    630         LOG(ERROR) << "Failed to create bookmark node with title "
    631                    << src.GetTitle() + " and url "
    632                    << src.GetBookmarkSpecifics().url();
    633         continue;
    634       }
    635       model_associator_->Associate(dst, src.GetId());
    636     }
    637 
    638     to_reposition.insert(std::make_pair(src.GetPositionIndex(), dst));
    639     bookmark_model_->SetNodeMetaInfo(dst, kBookmarkTransactionVersionKey,
    640                                      base::Int64ToString(model_version));
    641   }
    642 
    643   // When we added or updated bookmarks in the previous loop, we placed them to
    644   // the far right position.  Now we iterate over all these modified items in
    645   // sync order, left to right, moving them into their proper positions.
    646   for (std::multimap<int, const BookmarkNode*>::iterator it =
    647        to_reposition.begin(); it != to_reposition.end(); ++it) {
    648     const BookmarkNode* parent = it->second->parent();
    649     model->Move(it->second, parent, it->first);
    650   }
    651 
    652   // Clean up the temporary node.
    653   if (foster_parent) {
    654     // There should be no nodes left under the foster parent.
    655     DCHECK_EQ(foster_parent->child_count(), 0);
    656     model->Remove(foster_parent->parent(),
    657                   foster_parent->parent()->GetIndexOf(foster_parent));
    658     foster_parent = NULL;
    659   }
    660 
    661   // The visibility of the mobile node may need to change.
    662   model_associator_->UpdatePermanentNodeVisibility();
    663 
    664   // We are now ready to hear about bookmarks changes again.
    665   model->AddObserver(this);
    666 
    667   // All changes are applied in bookmark model. Set transaction version on
    668   // bookmark model to mark as synced.
    669   model->SetNodeMetaInfo(model->root_node(), kBookmarkTransactionVersionKey,
    670                          base::Int64ToString(model_version));
    671 }
    672 
    673 // Static.
    674 // Update a bookmark node with specified sync data.
    675 void BookmarkChangeProcessor::UpdateBookmarkWithSyncData(
    676     const syncer::BaseNode& sync_node,
    677     BookmarkModel* model,
    678     const BookmarkNode* node,
    679     Profile* profile) {
    680   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
    681   const sync_pb::BookmarkSpecifics& specifics =
    682       sync_node.GetBookmarkSpecifics();
    683   if (!sync_node.GetIsFolder())
    684     model->SetURL(node, GURL(specifics.url()));
    685   model->SetTitle(node, UTF8ToUTF16(sync_node.GetTitle()));
    686   if (specifics.has_creation_time_us()) {
    687     model->SetDateAdded(
    688         node,
    689         base::Time::FromInternalValue(specifics.creation_time_us()));
    690   }
    691   SetBookmarkFavicon(&sync_node, node, model, profile);
    692 }
    693 
    694 // static
    695 void BookmarkChangeProcessor::UpdateTransactionVersion(
    696     int64 new_version,
    697     BookmarkModel* model,
    698     const std::vector<const BookmarkNode*>& nodes) {
    699   if (new_version != syncer::syncable::kInvalidTransactionVersion) {
    700     model->SetNodeMetaInfo(model->root_node(), kBookmarkTransactionVersionKey,
    701                            base::Int64ToString(new_version));
    702     for (size_t i = 0; i < nodes.size(); ++i) {
    703       model->SetNodeMetaInfo(nodes[i], kBookmarkTransactionVersionKey,
    704                              base::Int64ToString(new_version));
    705     }
    706   }
    707 }
    708 
    709 // static
    710 // Creates a bookmark node under the given parent node from the given sync
    711 // node. Returns the newly created node.
    712 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
    713     syncer::BaseNode* sync_node,
    714     const BookmarkNode* parent,
    715     BookmarkModel* model,
    716     Profile* profile,
    717     int index) {
    718   DCHECK(parent);
    719 
    720   const BookmarkNode* node;
    721   if (sync_node->GetIsFolder()) {
    722     node = model->AddFolder(parent, index, UTF8ToUTF16(sync_node->GetTitle()));
    723   } else {
    724     // 'creation_time_us' was added in m24. Assume a time of 0 means now.
    725     const sync_pb::BookmarkSpecifics& specifics =
    726         sync_node->GetBookmarkSpecifics();
    727     const int64 create_time_internal = specifics.creation_time_us();
    728     base::Time create_time = (create_time_internal == 0) ?
    729         base::Time::Now() : base::Time::FromInternalValue(create_time_internal);
    730     node = model->AddURLWithCreationTime(parent, index,
    731                                          UTF8ToUTF16(sync_node->GetTitle()),
    732                                          GURL(specifics.url()), create_time);
    733     if (node)
    734       SetBookmarkFavicon(sync_node, node, model, profile);
    735   }
    736   return node;
    737 }
    738 
    739 // static
    740 // Sets the favicon of the given bookmark node from the given sync node.
    741 bool BookmarkChangeProcessor::SetBookmarkFavicon(
    742     const syncer::BaseNode* sync_node,
    743     const BookmarkNode* bookmark_node,
    744     BookmarkModel* bookmark_model,
    745     Profile* profile) {
    746   const sync_pb::BookmarkSpecifics& specifics =
    747       sync_node->GetBookmarkSpecifics();
    748   const std::string& icon_bytes_str = specifics.favicon();
    749   if (icon_bytes_str.empty())
    750     return false;
    751 
    752   scoped_refptr<base::RefCountedString> icon_bytes(
    753       new base::RefCountedString());
    754   icon_bytes->data().assign(icon_bytes_str);
    755   GURL icon_url(specifics.icon_url());
    756 
    757   // Old clients may not be syncing the favicon URL. If the icon URL is not
    758   // synced, use the page URL as a fake icon URL as it is guaranteed to be
    759   // unique.
    760   if (icon_url.is_empty())
    761     icon_url = bookmark_node->url();
    762 
    763   ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
    764 
    765   return true;
    766 }
    767 
    768 // static
    769 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
    770     const BookmarkNode* bookmark_node,
    771     Profile* profile,
    772     const GURL& icon_url,
    773     const scoped_refptr<base::RefCountedMemory>& bitmap_data) {
    774   HistoryService* history =
    775       HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
    776   FaviconService* favicon_service =
    777       FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
    778 
    779   history->AddPageNoVisitForBookmark(bookmark_node->url(),
    780                                      bookmark_node->GetTitle());
    781   // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
    782   // overwrite the cached 2x favicon bitmap. Sync favicons are always
    783   // gfx::kFaviconSize in width and height. Store the favicon into history
    784   // as such.
    785   gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
    786   favicon_service->MergeFavicon(bookmark_node->url(),
    787                                 icon_url,
    788                                 chrome::FAVICON,
    789                                 bitmap_data,
    790                                 pixel_size);
    791 }
    792 
    793 // static
    794 void BookmarkChangeProcessor::SetSyncNodeFavicon(
    795     const BookmarkNode* bookmark_node,
    796     BookmarkModel* model,
    797     syncer::WriteNode* sync_node) {
    798   scoped_refptr<base::RefCountedMemory> favicon_bytes(NULL);
    799   EncodeFavicon(bookmark_node, model, &favicon_bytes);
    800   if (favicon_bytes.get() && favicon_bytes->size()) {
    801     sync_pb::BookmarkSpecifics updated_specifics(
    802         sync_node->GetBookmarkSpecifics());
    803     updated_specifics.set_favicon(favicon_bytes->front(),
    804                                   favicon_bytes->size());
    805     updated_specifics.set_icon_url(bookmark_node->icon_url().spec());
    806     sync_node->SetBookmarkSpecifics(updated_specifics);
    807   }
    808 }
    809 
    810 }  // namespace browser_sync
    811