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