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