Home | History | Annotate | Download | only in glue
      1 // Copyright (c) 2011 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 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
      5 
      6 #include <stack>
      7 #include <vector>
      8 
      9 #include "base/string16.h"
     10 #include "base/string_util.h"
     11 
     12 #include "base/utf_string_conversions.h"
     13 #include "chrome/browser/bookmarks/bookmark_utils.h"
     14 #include "chrome/browser/favicon_service.h"
     15 #include "chrome/browser/profiles/profile.h"
     16 #include "chrome/browser/sync/profile_sync_service.h"
     17 #include "content/browser/browser_thread.h"
     18 #include "third_party/skia/include/core/SkBitmap.h"
     19 #include "ui/gfx/codec/png_codec.h"
     20 
     21 namespace browser_sync {
     22 
     23 BookmarkChangeProcessor::BookmarkChangeProcessor(
     24     BookmarkModelAssociator* model_associator,
     25     UnrecoverableErrorHandler* error_handler)
     26     : ChangeProcessor(error_handler),
     27       bookmark_model_(NULL),
     28       model_associator_(model_associator) {
     29   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     30   DCHECK(model_associator);
     31   DCHECK(error_handler);
     32 }
     33 
     34 void BookmarkChangeProcessor::StartImpl(Profile* profile) {
     35   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     36   DCHECK(!bookmark_model_);
     37   bookmark_model_ = profile->GetBookmarkModel();
     38   DCHECK(bookmark_model_->IsLoaded());
     39   bookmark_model_->AddObserver(this);
     40 }
     41 
     42 void BookmarkChangeProcessor::StopImpl() {
     43   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     44   DCHECK(bookmark_model_);
     45   bookmark_model_->RemoveObserver(this);
     46   bookmark_model_ = NULL;
     47   model_associator_ = NULL;
     48 }
     49 
     50 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
     51     const BookmarkNode* src, BookmarkModel* model, sync_api::WriteNode* dst) {
     52   // Set the properties of the item.
     53   dst->SetIsFolder(src->is_folder());
     54   dst->SetTitle(UTF16ToWideHack(src->GetTitle()));
     55   if (!src->is_folder())
     56     dst->SetURL(src->GetURL());
     57   SetSyncNodeFavicon(src, model, dst);
     58 }
     59 
     60 // static
     61 void BookmarkChangeProcessor::EncodeFavicon(const BookmarkNode* src,
     62                                             BookmarkModel* model,
     63                                             std::vector<unsigned char>* dst) {
     64   const SkBitmap& favicon = model->GetFavicon(src);
     65 
     66   dst->clear();
     67 
     68   // Check for zero-dimension images.  This can happen if the favicon is
     69   // still being loaded.
     70   if (favicon.empty())
     71     return;
     72 
     73   // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
     74   // sync subsystem.
     75   if (!gfx::PNGCodec::EncodeBGRASkBitmap(favicon, false, dst))
     76     return;
     77 }
     78 
     79 void BookmarkChangeProcessor::RemoveOneSyncNode(
     80     sync_api::WriteTransaction* trans, const BookmarkNode* node) {
     81   sync_api::WriteNode sync_node(trans);
     82   if (!model_associator_->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
     83     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
     84     return;
     85   }
     86   // This node should have no children.
     87   DCHECK(sync_node.GetFirstChildId() == sync_api::kInvalidId);
     88   // Remove association and delete the sync node.
     89   model_associator_->Disassociate(sync_node.GetId());
     90   sync_node.Remove();
     91 }
     92 
     93 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
     94     const BookmarkNode* topmost) {
     95   sync_api::WriteTransaction trans(share_handle());
     96 
     97   // Later logic assumes that |topmost| has been unlinked.
     98   DCHECK(!topmost->parent());
     99 
    100   // A BookmarkModel deletion event means that |node| and all its children were
    101   // deleted. Sync backend expects children to be deleted individually, so we do
    102   // a depth-first-search here.  At each step, we consider the |index|-th child
    103   // of |node|.  |index_stack| stores index values for the parent levels.
    104   std::stack<int> index_stack;
    105   index_stack.push(0);  // For the final pop.  It's never used.
    106   const BookmarkNode* node = topmost;
    107   int index = 0;
    108   while (node) {
    109     // The top of |index_stack| should always be |node|'s index.
    110     DCHECK(!node->parent() || (node->parent()->GetIndexOf(node) ==
    111       index_stack.top()));
    112     if (index == node->child_count()) {
    113       // If we've processed all of |node|'s children, delete |node| and move
    114       // on to its successor.
    115       RemoveOneSyncNode(&trans, node);
    116       node = node->parent();
    117       index = index_stack.top() + 1;      // (top() + 0) was what we removed.
    118       index_stack.pop();
    119     } else {
    120       // If |node| has an unprocessed child, process it next after pushing the
    121       // current state onto the stack.
    122       DCHECK_LT(index, node->child_count());
    123       index_stack.push(index);
    124       node = node->GetChild(index);
    125       index = 0;
    126     }
    127   }
    128   DCHECK(index_stack.empty());  // Nothing should be left on the stack.
    129 }
    130 
    131 void BookmarkChangeProcessor::Loaded(BookmarkModel* model) {
    132   NOTREACHED();
    133 }
    134 
    135 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
    136     BookmarkModel* model) {
    137   DCHECK(!running()) << "BookmarkModel deleted while ChangeProcessor running.";
    138   bookmark_model_ = NULL;
    139 }
    140 
    141 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
    142                                                 const BookmarkNode* parent,
    143                                                 int index) {
    144   DCHECK(running());
    145   DCHECK(share_handle());
    146 
    147   // Acquire a scoped write lock via a transaction.
    148   sync_api::WriteTransaction trans(share_handle());
    149 
    150   CreateSyncNode(parent, model, index, &trans, model_associator_,
    151                  error_handler());
    152 }
    153 
    154 // static
    155 int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent,
    156     BookmarkModel* model, int index, sync_api::WriteTransaction* trans,
    157     BookmarkModelAssociator* associator,
    158     UnrecoverableErrorHandler* error_handler) {
    159   const BookmarkNode* child = parent->GetChild(index);
    160   DCHECK(child);
    161 
    162   // Create a WriteNode container to hold the new node.
    163   sync_api::WriteNode sync_child(trans);
    164 
    165   // Actually create the node with the appropriate initial position.
    166   if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) {
    167     error_handler->OnUnrecoverableError(FROM_HERE,
    168         "Sync node creation failed; recovery unlikely");
    169     return sync_api::kInvalidId;
    170   }
    171 
    172   UpdateSyncNodeProperties(child, model, &sync_child);
    173 
    174   // Associate the ID from the sync domain with the bookmark node, so that we
    175   // can refer back to this item later.
    176   associator->Associate(child, sync_child.GetId());
    177 
    178   return sync_child.GetId();
    179 }
    180 
    181 
    182 void BookmarkChangeProcessor::BookmarkNodeRemoved(BookmarkModel* model,
    183                                                     const BookmarkNode* parent,
    184                                                     int index,
    185                                                     const BookmarkNode* node) {
    186   DCHECK(running());
    187   RemoveSyncNodeHierarchy(node);
    188 }
    189 
    190 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model,
    191                                                     const BookmarkNode* node) {
    192   DCHECK(running());
    193   // We shouldn't see changes to the top-level nodes.
    194   if (node == model->GetBookmarkBarNode() || node == model->other_node()) {
    195     NOTREACHED() << "Saw update to permanent node!";
    196     return;
    197   }
    198 
    199   // Acquire a scoped write lock via a transaction.
    200   sync_api::WriteTransaction trans(share_handle());
    201 
    202   // Lookup the sync node that's associated with |node|.
    203   sync_api::WriteNode sync_node(&trans);
    204   if (!model_associator_->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
    205     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
    206     return;
    207   }
    208 
    209   UpdateSyncNodeProperties(node, model, &sync_node);
    210 
    211   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
    212   DCHECK_EQ(model_associator_->GetChromeNodeFromSyncId(
    213             sync_node.GetParentId()),
    214             node->parent());
    215   // This node's index should be one more than the predecessor's index.
    216   DCHECK_EQ(node->parent()->GetIndexOf(node),
    217             CalculateBookmarkModelInsertionIndex(node->parent(),
    218                                                  &sync_node));
    219 }
    220 
    221 
    222 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model,
    223       const BookmarkNode* old_parent, int old_index,
    224       const BookmarkNode* new_parent, int new_index) {
    225   DCHECK(running());
    226   const BookmarkNode* child = new_parent->GetChild(new_index);
    227   // We shouldn't see changes to the top-level nodes.
    228   if (child == model->GetBookmarkBarNode() || child == model->other_node()) {
    229     NOTREACHED() << "Saw update to permanent node!";
    230     return;
    231   }
    232 
    233   // Acquire a scoped write lock via a transaction.
    234   sync_api::WriteTransaction trans(share_handle());
    235 
    236   // Lookup the sync node that's associated with |child|.
    237   sync_api::WriteNode sync_node(&trans);
    238   if (!model_associator_->InitSyncNodeFromChromeId(child->id(), &sync_node)) {
    239     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
    240     return;
    241   }
    242 
    243   if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node,
    244                      model_associator_)) {
    245     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
    246     return;
    247   }
    248 }
    249 
    250 void BookmarkChangeProcessor::BookmarkNodeFaviconLoaded(BookmarkModel* model,
    251       const BookmarkNode* node) {
    252   DCHECK(running());
    253   BookmarkNodeChanged(model, node);
    254 }
    255 
    256 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
    257     BookmarkModel* model, const BookmarkNode* node) {
    258 
    259   // Acquire a scoped write lock via a transaction.
    260   sync_api::WriteTransaction trans(share_handle());
    261 
    262   // The given node's children got reordered. We need to reorder all the
    263   // children of the corresponding sync node.
    264   for (int i = 0; i < node->child_count(); ++i) {
    265     sync_api::WriteNode sync_child(&trans);
    266     if (!model_associator_->InitSyncNodeFromChromeId(node->GetChild(i)->id(),
    267                                                      &sync_child)) {
    268       error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
    269       return;
    270     }
    271     DCHECK_EQ(sync_child.GetParentId(),
    272               model_associator_->GetSyncIdFromChromeId(node->id()));
    273 
    274     if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child,
    275                        model_associator_)) {
    276       error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
    277       return;
    278     }
    279   }
    280 }
    281 
    282 // static
    283 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation,
    284       const BookmarkNode* parent, int index, sync_api::WriteTransaction* trans,
    285       sync_api::WriteNode* dst, BookmarkModelAssociator* associator) {
    286   sync_api::ReadNode sync_parent(trans);
    287   if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) {
    288     LOG(WARNING) << "Parent lookup failed";
    289     return false;
    290   }
    291 
    292   bool success = false;
    293   if (index == 0) {
    294     // Insert into first position.
    295     success = (operation == CREATE) ?
    296         dst->InitByCreation(syncable::BOOKMARKS, sync_parent, NULL) :
    297         dst->SetPosition(sync_parent, NULL);
    298     if (success) {
    299       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
    300       DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
    301       DCHECK_EQ(dst->GetPredecessorId(), sync_api::kInvalidId);
    302     }
    303   } else {
    304     // Find the bookmark model predecessor, and insert after it.
    305     const BookmarkNode* prev = parent->GetChild(index - 1);
    306     sync_api::ReadNode sync_prev(trans);
    307     if (!associator->InitSyncNodeFromChromeId(prev->id(), &sync_prev)) {
    308       LOG(WARNING) << "Predecessor lookup failed";
    309       return false;
    310     }
    311     success = (operation == CREATE) ?
    312         dst->InitByCreation(syncable::BOOKMARKS, sync_parent, &sync_prev) :
    313         dst->SetPosition(sync_parent, &sync_prev);
    314     if (success) {
    315       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
    316       DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
    317       DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
    318     }
    319   }
    320   return success;
    321 }
    322 
    323 // Determine the bookmark model index to which a node must be moved so that
    324 // predecessor of the node (in the bookmark model) matches the predecessor of
    325 // |source| (in the sync model).
    326 // As a precondition, this assumes that the predecessor of |source| has been
    327 // updated and is already in the correct position in the bookmark model.
    328 int BookmarkChangeProcessor::CalculateBookmarkModelInsertionIndex(
    329     const BookmarkNode* parent,
    330     const sync_api::BaseNode* child_info) const {
    331   DCHECK(parent);
    332   DCHECK(child_info);
    333   int64 predecessor_id = child_info->GetPredecessorId();
    334   // A return ID of kInvalidId indicates no predecessor.
    335   if (predecessor_id == sync_api::kInvalidId)
    336     return 0;
    337 
    338   // Otherwise, insert after the predecessor bookmark node.
    339   const BookmarkNode* predecessor =
    340       model_associator_->GetChromeNodeFromSyncId(predecessor_id);
    341   DCHECK(predecessor);
    342   DCHECK_EQ(predecessor->parent(), parent);
    343   return parent->GetIndexOf(predecessor) + 1;
    344 }
    345 
    346 // ApplyModelChanges is called by the sync backend after changes have been made
    347 // to the sync engine's model.  Apply these changes to the browser bookmark
    348 // model.
    349 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
    350     const sync_api::BaseTransaction* trans,
    351     const sync_api::SyncManager::ChangeRecord* changes,
    352     int change_count) {
    353   if (!running())
    354     return;
    355   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    356   // A note about ordering.  Sync backend is responsible for ordering the change
    357   // records in the following order:
    358   //
    359   // 1. Deletions, from leaves up to parents.
    360   // 2. Existing items with synced parents & predecessors.
    361   // 3. New items with synced parents & predecessors.
    362   // 4. Items with parents & predecessors in the list.
    363   // 5. Repeat #4 until all items are in the list.
    364   //
    365   // "Predecessor" here means the previous item within a given folder; an item
    366   // in the first position is always said to have a synced predecessor.
    367   // For the most part, applying these changes in the order given will yield
    368   // the correct result.  There is one exception, however: for items that are
    369   // moved away from a folder that is being deleted, we will process the delete
    370   // before the move.  Since deletions in the bookmark model propagate from
    371   // parent to child, we must move them to a temporary location.
    372   BookmarkModel* model = bookmark_model_;
    373 
    374   // We are going to make changes to the bookmarks model, but don't want to end
    375   // up in a feedback loop, so remove ourselves as an observer while applying
    376   // changes.
    377   model->RemoveObserver(this);
    378 
    379   // A parent to hold nodes temporarily orphaned by parent deletion.  It is
    380   // lazily created inside the loop.
    381   const BookmarkNode* foster_parent = NULL;
    382   for (int i = 0; i < change_count; ++i) {
    383     const BookmarkNode* dst =
    384         model_associator_->GetChromeNodeFromSyncId(changes[i].id);
    385     // Ignore changes to the permanent top-level nodes.  We only care about
    386     // their children.
    387     if ((dst == model->GetBookmarkBarNode()) || (dst == model->other_node()))
    388       continue;
    389     if (changes[i].action ==
    390         sync_api::SyncManager::ChangeRecord::ACTION_DELETE) {
    391       // Deletions should always be at the front of the list.
    392       DCHECK(i == 0 || changes[i-1].action == changes[i].action);
    393       // Children of a deleted node should not be deleted; they may be
    394       // reparented by a later change record.  Move them to a temporary place.
    395       DCHECK(dst) << "Could not find node to be deleted";
    396       if (!dst) // Can't do anything if we can't find the chrome node.
    397         continue;
    398       const BookmarkNode* parent = dst->parent();
    399       if (dst->child_count()) {
    400         if (!foster_parent) {
    401           foster_parent = model->AddFolder(model->other_node(),
    402                                            model->other_node()->child_count(),
    403                                            string16());
    404         }
    405         for (int i = dst->child_count() - 1; i >= 0; --i) {
    406           model->Move(dst->GetChild(i), foster_parent,
    407                       foster_parent->child_count());
    408         }
    409       }
    410       DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
    411       model_associator_->Disassociate(changes[i].id);
    412       int index = parent->GetIndexOf(dst);
    413       if (index > -1)
    414         model->Remove(parent, index);
    415       dst = NULL;
    416     } else {
    417       DCHECK_EQ((changes[i].action ==
    418           sync_api::SyncManager::ChangeRecord::ACTION_ADD), (dst == NULL))
    419           << "ACTION_ADD should be seen if and only if the node is unknown.";
    420 
    421       sync_api::ReadNode src(trans);
    422       if (!src.InitByIdLookup(changes[i].id)) {
    423         error_handler()->OnUnrecoverableError(FROM_HERE,
    424             "ApplyModelChanges was passed a bad ID");
    425         return;
    426       }
    427 
    428       CreateOrUpdateBookmarkNode(&src, model);
    429     }
    430   }
    431   // Clean up the temporary node.
    432   if (foster_parent) {
    433     // There should be no nodes left under the foster parent.
    434     DCHECK_EQ(foster_parent->child_count(), 0);
    435     model->Remove(foster_parent->parent(),
    436                   foster_parent->parent()->GetIndexOf(foster_parent));
    437     foster_parent = NULL;
    438   }
    439 
    440   // We are now ready to hear about bookmarks changes again.
    441   model->AddObserver(this);
    442 }
    443 
    444 // Create a bookmark node corresponding to |src| if one is not already
    445 // associated with |src|.
    446 const BookmarkNode* BookmarkChangeProcessor::CreateOrUpdateBookmarkNode(
    447     sync_api::BaseNode* src,
    448     BookmarkModel* model) {
    449   const BookmarkNode* parent =
    450       model_associator_->GetChromeNodeFromSyncId(src->GetParentId());
    451   if (!parent) {
    452     DLOG(WARNING) << "Could not find parent of node being added/updated."
    453       << " Node title: " << src->GetTitle()
    454       << ", parent id = " << src->GetParentId();
    455 
    456     return NULL;
    457   }
    458   int index = CalculateBookmarkModelInsertionIndex(parent, src);
    459   const BookmarkNode* dst = model_associator_->GetChromeNodeFromSyncId(
    460       src->GetId());
    461   if (!dst) {
    462     dst = CreateBookmarkNode(src, parent, model, index);
    463     model_associator_->Associate(dst, src->GetId());
    464   } else {
    465     // URL and is_folder are not expected to change.
    466     // TODO(ncarter): Determine if such changes should be legal or not.
    467     DCHECK_EQ(src->GetIsFolder(), dst->is_folder());
    468 
    469     // Handle reparenting and/or repositioning.
    470     model->Move(dst, parent, index);
    471 
    472     if (!src->GetIsFolder())
    473       model->SetURL(dst, src->GetURL());
    474     model->SetTitle(dst, WideToUTF16Hack(src->GetTitle()));
    475 
    476     SetBookmarkFavicon(src, dst, model);
    477   }
    478 
    479   return dst;
    480 }
    481 
    482 // static
    483 // Creates a bookmark node under the given parent node from the given sync
    484 // node. Returns the newly created node.
    485 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
    486     sync_api::BaseNode* sync_node,
    487     const BookmarkNode* parent,
    488     BookmarkModel* model,
    489     int index) {
    490   DCHECK(parent);
    491   DCHECK(index >= 0 && index <= parent->child_count());
    492 
    493   const BookmarkNode* node;
    494   if (sync_node->GetIsFolder()) {
    495     node = model->AddFolder(parent, index,
    496                             WideToUTF16Hack(sync_node->GetTitle()));
    497   } else {
    498     node = model->AddURL(parent, index,
    499                          WideToUTF16Hack(sync_node->GetTitle()),
    500                          sync_node->GetURL());
    501     SetBookmarkFavicon(sync_node, node, model);
    502   }
    503   return node;
    504 }
    505 
    506 // static
    507 // Sets the favicon of the given bookmark node from the given sync node.
    508 bool BookmarkChangeProcessor::SetBookmarkFavicon(
    509     sync_api::BaseNode* sync_node,
    510     const BookmarkNode* bookmark_node,
    511     BookmarkModel* bookmark_model) {
    512   std::vector<unsigned char> icon_bytes_vector;
    513   sync_node->GetFaviconBytes(&icon_bytes_vector);
    514   if (icon_bytes_vector.empty())
    515     return false;
    516 
    517   ApplyBookmarkFavicon(bookmark_node, bookmark_model->profile(),
    518                        icon_bytes_vector);
    519 
    520   return true;
    521 }
    522 
    523 // static
    524 // Applies the given favicon bytes vector to the given bookmark node.
    525 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
    526     const BookmarkNode* bookmark_node,
    527     Profile* profile,
    528     const std::vector<unsigned char>& icon_bytes_vector) {
    529   // Registering a favicon requires that we provide a source URL, but we
    530   // don't know where these came from.  Currently we just use the
    531   // destination URL, which is not correct, but since the favicon URL
    532   // is used as a key in the history's thumbnail DB, this gives us a value
    533   // which does not collide with others.
    534   GURL fake_icon_url = bookmark_node->GetURL();
    535 
    536   HistoryService* history =
    537       profile->GetHistoryService(Profile::EXPLICIT_ACCESS);
    538   FaviconService* favicon_service =
    539       profile->GetFaviconService(Profile::EXPLICIT_ACCESS);
    540 
    541   history->AddPageNoVisitForBookmark(bookmark_node->GetURL());
    542   favicon_service->SetFavicon(bookmark_node->GetURL(),
    543                               fake_icon_url,
    544                               icon_bytes_vector,
    545                               history::FAVICON);
    546 }
    547 
    548 // static
    549 void BookmarkChangeProcessor::SetSyncNodeFavicon(
    550     const BookmarkNode* bookmark_node,
    551     BookmarkModel* model,
    552     sync_api::WriteNode* sync_node) {
    553   std::vector<unsigned char> favicon_bytes;
    554   EncodeFavicon(bookmark_node, model, &favicon_bytes);
    555   if (!favicon_bytes.empty())
    556     sync_node->SetFaviconBytes(favicon_bytes);
    557 }
    558 
    559 }  // namespace browser_sync
    560