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