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