Commit 593d160f authored by zea@chromium.org's avatar zea@chromium.org

[Sync] Make BookmarkChangeProcessor resilient against updates before add

Other bookmarks observer might make their own changes on add, triggering updates
before the add call arrives. Be resilient against this.

BUG=369728

Review URL: https://codereview.chromium.org/277033002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@269645 0039d316-1c4b-4281-b951-d872f2087c98
parent 73bae9d5
...@@ -205,29 +205,27 @@ void BookmarkChangeProcessor::RemoveAllChildNodes( ...@@ -205,29 +205,27 @@ void BookmarkChangeProcessor::RemoveAllChildNodes(
} }
} }
void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model, void BookmarkChangeProcessor::CreateOrUpdateSyncNode(const BookmarkNode* node) {
bool ids_reassigned) { const BookmarkNode* parent = node->parent();
NOTREACHED(); int index = node->parent()->GetIndexOf(node);
}
void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
BookmarkModel* model) {
NOTREACHED();
bookmark_model_ = NULL;
}
void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
DCHECK(share_handle());
int64 new_version = syncer::syncable::kInvalidTransactionVersion; int64 new_version = syncer::syncable::kInvalidTransactionVersion;
int64 sync_id = syncer::kInvalidId; int64 sync_id = syncer::kInvalidId;
{ {
// Acquire a scoped write lock via a transaction. // Acquire a scoped write lock via a transaction.
syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
sync_id = CreateSyncNode(parent, model, index, &trans, sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
model_associator_, error_handler()); if (sync_id != syncer::kInvalidId) {
UpdateSyncNode(
node, bookmark_model_, &trans, model_associator_, error_handler());
} else {
sync_id = CreateSyncNode(parent,
bookmark_model_,
index,
&trans,
model_associator_,
error_handler());
}
} }
if (syncer::kInvalidId != sync_id) { if (syncer::kInvalidId != sync_id) {
...@@ -236,11 +234,29 @@ void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model, ...@@ -236,11 +234,29 @@ void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
// of added node here. After switching to ordinals for positioning, // of added node here. After switching to ordinals for positioning,
// PREV_ID/NEXT_ID will be deprecated and siblings will not be updated. // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
UpdateTransactionVersion( UpdateTransactionVersion(
new_version, model, new_version,
bookmark_model_,
std::vector<const BookmarkNode*>(1, parent->GetChild(index))); std::vector<const BookmarkNode*>(1, parent->GetChild(index)));
} }
} }
void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
bool ids_reassigned) {
NOTREACHED();
}
void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) {
NOTREACHED();
bookmark_model_ = NULL;
}
void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
DCHECK(share_handle());
CreateOrUpdateSyncNode(parent->GetChild(index));
}
// static // static
int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent, int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent,
BookmarkModel* model, int index, syncer::WriteTransaction* trans, BookmarkModel* model, int index, syncer::WriteTransaction* trans,
...@@ -290,77 +306,29 @@ void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model, ...@@ -290,77 +306,29 @@ void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model,
NOTREACHED() << "Saw update to permanent node!"; NOTREACHED() << "Saw update to permanent node!";
return; return;
} }
CreateOrUpdateSyncNode(node);
}
int64 new_version = syncer::syncable::kInvalidTransactionVersion; // Static.
{ int64 BookmarkChangeProcessor::UpdateSyncNode(
// Acquire a scoped write lock via a transaction. const BookmarkNode* node,
syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); BookmarkModel* model,
syncer::WriteTransaction* trans,
// Lookup the sync node that's associated with |node|. BookmarkModelAssociator* associator,
syncer::WriteNode sync_node(&trans); DataTypeErrorHandler* error_handler) {
if (!model_associator_->InitSyncNodeFromChromeId(node->id(), &sync_node)) { // Lookup the sync node that's associated with |node|.
// TODO(tim): Investigating bug 121587. syncer::WriteNode sync_node(trans);
if (model_associator_->GetSyncIdFromChromeId(node->id()) == if (!associator->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
syncer::kInvalidId) { error_handler->OnSingleDatatypeUnrecoverableError(
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, FROM_HERE, "Could not load bookmark node on update.");
"Bookmark id not found in model associator on BookmarkNodeChanged"); return syncer::kInvalidId;
LOG(ERROR) << "Bad id.";
} else if (!sync_node.GetEntry()->good()) {
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
"Could not InitByIdLookup on BookmarkNodeChanged, good() failed");
LOG(ERROR) << "Bad entry.";
} else if (sync_node.GetEntry()->GetIsDel()) {
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
"Could not InitByIdLookup on BookmarkNodeChanged, is_del true");
LOG(ERROR) << "Deleted entry.";
} else {
syncer::Cryptographer* crypto = trans.GetCryptographer();
syncer::ModelTypeSet encrypted_types(trans.GetEncryptedTypes());
const sync_pb::EntitySpecifics& specifics =
sync_node.GetEntry()->GetSpecifics();
CHECK(specifics.has_encrypted());
const bool can_decrypt = crypto->CanDecrypt(specifics.encrypted());
const bool agreement = encrypted_types.Has(syncer::BOOKMARKS);
if (!agreement && !can_decrypt) {
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
"Could not InitByIdLookup on BookmarkNodeChanged, "
" Cryptographer thinks bookmarks not encrypted, and CanDecrypt"
" failed.");
LOG(ERROR) << "Case 1.";
} else if (agreement && can_decrypt) {
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
"Could not InitByIdLookup on BookmarkNodeChanged, "
" Cryptographer thinks bookmarks are encrypted, and CanDecrypt"
" succeeded (?!), but DecryptIfNecessary failed.");
LOG(ERROR) << "Case 2.";
} else if (agreement) {
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
"Could not InitByIdLookup on BookmarkNodeChanged, "
" Cryptographer thinks bookmarks are encrypted, but CanDecrypt"
" failed.");
LOG(ERROR) << "Case 3.";
} else {
error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
"Could not InitByIdLookup on BookmarkNodeChanged, "
" Cryptographer thinks bookmarks not encrypted, but CanDecrypt"
" succeeded (super weird, btw)");
LOG(ERROR) << "Case 4.";
}
}
return;
}
UpdateSyncNodeProperties(node, model, &sync_node);
DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
DCHECK_EQ(model_associator_->GetChromeNodeFromSyncId(
sync_node.GetParentId()),
node->parent());
DCHECK_EQ(node->parent()->GetIndexOf(node), sync_node.GetPositionIndex());
} }
UpdateSyncNodeProperties(node, model, &sync_node);
UpdateTransactionVersion(new_version, model, DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
std::vector<const BookmarkNode*>(1, node)); DCHECK_EQ(associator->GetChromeNodeFromSyncId(sync_node.GetParentId()),
node->parent());
DCHECK_EQ(node->parent()->GetIndexOf(node), sync_node.GetPositionIndex());
return sync_node.GetId();
} }
void BookmarkChangeProcessor::BookmarkMetaInfoChanged( void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
......
...@@ -133,6 +133,13 @@ class BookmarkChangeProcessor : public BookmarkModelObserver, ...@@ -133,6 +133,13 @@ class BookmarkChangeProcessor : public BookmarkModelObserver,
BookmarkModelAssociator* associator, BookmarkModelAssociator* associator,
DataTypeErrorHandler* error_handler); DataTypeErrorHandler* error_handler);
// Update |bookmark_node|'s sync node.
static int64 UpdateSyncNode(const BookmarkNode* bookmark_node,
BookmarkModel* model,
syncer::WriteTransaction* trans,
BookmarkModelAssociator* associator,
DataTypeErrorHandler* error_handler);
// Update transaction version of |model| and |nodes| to |new_version| if // Update transaction version of |model| and |nodes| to |new_version| if
// it's valid. // it's valid.
static void UpdateTransactionVersion( static void UpdateTransactionVersion(
...@@ -196,6 +203,9 @@ class BookmarkChangeProcessor : public BookmarkModelObserver, ...@@ -196,6 +203,9 @@ class BookmarkChangeProcessor : public BookmarkModelObserver,
// Remove all the sync nodes associated with |node| and its children. // Remove all the sync nodes associated with |node| and its children.
void RemoveSyncNodeHierarchy(const BookmarkNode* node); void RemoveSyncNodeHierarchy(const BookmarkNode* node);
// Creates or updates a sync node associated with |node|.
void CreateOrUpdateSyncNode(const BookmarkNode* node);
// The bookmark model we are processing changes from. Non-NULL when // The bookmark model we are processing changes from. Non-NULL when
// |running_| is true. // |running_| is true.
BookmarkModel* bookmark_model_; BookmarkModel* bookmark_model_;
......
...@@ -725,6 +725,7 @@ class ProfileSyncServiceBookmarkTest : public testing::Test { ...@@ -725,6 +725,7 @@ class ProfileSyncServiceBookmarkTest : public testing::Test {
} }
protected: protected:
TestingProfile profile_;
BookmarkModel* model_; BookmarkModel* model_;
syncer::TestUserShare test_user_share_; syncer::TestUserShare test_user_share_;
scoped_ptr<BookmarkChangeProcessor> change_processor_; scoped_ptr<BookmarkChangeProcessor> change_processor_;
...@@ -740,8 +741,6 @@ class ProfileSyncServiceBookmarkTest : public testing::Test { ...@@ -740,8 +741,6 @@ class ProfileSyncServiceBookmarkTest : public testing::Test {
syncer::SyncMergeResult local_merge_result_; syncer::SyncMergeResult local_merge_result_;
syncer::SyncMergeResult syncer_merge_result_; syncer::SyncMergeResult syncer_merge_result_;
TestingProfile profile_;
}; };
TEST_F(ProfileSyncServiceBookmarkTest, InitialState) { TEST_F(ProfileSyncServiceBookmarkTest, InitialState) {
...@@ -2136,6 +2135,39 @@ TEST_F(ProfileSyncServiceBookmarkTestWithData, PersistenceError) { ...@@ -2136,6 +2135,39 @@ TEST_F(ProfileSyncServiceBookmarkTestWithData, PersistenceError) {
EXPECT_FALSE(AssociateModels()); EXPECT_FALSE(AssociateModels());
} }
// It's possible for update/add calls from the bookmark model to be out of
// order, or asynchronous. Handle that without triggering an error.
TEST_F(ProfileSyncServiceBookmarkTest, UpdateThenAdd) {
LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
StartSync();
EXPECT_TRUE(other_bookmarks_id());
EXPECT_TRUE(bookmark_bar_id());
EXPECT_TRUE(mobile_bookmarks_id());
ExpectModelMatch();
// Now destroy the change processor then add a bookmark, to simulate
// missing the Update call.
change_processor_.reset();
const BookmarkNode* node = model_->AddURL(model_->bookmark_bar_node(),
0,
base::ASCIIToUTF16("title"),
GURL("http://www.url.com"));
// Recreate the change processor then update that bookmark. Sync should
// receive the update call and gracefully treat that as if it were an add.
change_processor_.reset(new BookmarkChangeProcessor(
&profile_, model_associator_.get(), &mock_error_handler_));
change_processor_->Start(test_user_share_.user_share());
model_->SetTitle(node, base::ASCIIToUTF16("title2"));
ExpectModelMatch();
// Then simulate the add call arriving late.
change_processor_->BookmarkNodeAdded(model_, model_->bookmark_bar_node(), 0);
ExpectModelMatch();
}
} // namespace } // namespace
} // namespace browser_sync } // namespace browser_sync
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment