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 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 60 BookmarkChangeProcessor::~BookmarkChangeProcessor() { 61 if (bookmark_model_) 62 bookmark_model_->RemoveObserver(this); 63 } 64 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 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 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 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 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 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 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 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 245 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model, 246 bool ids_reassigned) { 247 NOTREACHED(); 248 } 249 250 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) { 251 NOTREACHED(); 252 bookmark_model_ = NULL; 253 } 254 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 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 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 298 void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved( 299 BookmarkModel* model, 300 const std::set<GURL>& removed_urls) { 301 RemoveAllSyncNodes(); 302 } 303 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. 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 336 void BookmarkChangeProcessor::BookmarkMetaInfoChanged( 337 BookmarkModel* model, const BookmarkNode* node) { 338 BookmarkNodeChanged(model, node); 339 } 340 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 376 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged( 377 BookmarkModel* model, 378 const BookmarkNode* node) { 379 BookmarkNodeChanged(model, node); 380 } 381 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 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. 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. 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 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. 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. 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> 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 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 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 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