Skip to content

Commit

Permalink
Remove dependency on server generated type root folders
Browse files Browse the repository at this point in the history
This change prepares the client to deal with implicit permanent folders
in Sync server updates. That includes the following:
- Expect type root folders to not come from the server on the initial
  sync. The client auto-creates type root folders for all types except
  Bookmarks and Nigori when progress marker changes from empty to
  non-empty. These folders are created as local nodes (with client IDs)
  that aren't expected to sync back to the server.
- Expect empty parent IDs in updates for both new and existing items.
- Because the client code updates first, the client must expect server
  to override locally created type root folders. To enable that the
  directory update code that matches update entities to local entities
  was updated to look at server unique tags in addition to client
  unique tags. Later when the server stops generating folders we
  should be able to remove that code.
- Added some extra special cases in Directory::CheckTreeInvariants to
  deal with client side created type root folders.
- Added / modified a few tests to cover cases with implicit parent IDs
  in updates.

BUG=438313

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

Cr-Commit-Position: refs/heads/master@{#313977}
  • Loading branch information
stanisc authored and Commit bot committed Jan 30, 2015
1 parent 39cd211 commit 8d4046a
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 128 deletions.
30 changes: 30 additions & 0 deletions sync/engine/directory_update_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "sync/engine/update_applicator.h"
#include "sync/sessions/directory_type_debug_info_emitter.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/model_neutral_mutable_entry.h"
#include "sync/syncable/syncable_model_neutral_write_transaction.h"
#include "sync/syncable/syncable_write_transaction.h"

Expand Down Expand Up @@ -65,16 +66,42 @@ SyncerError DirectoryUpdateHandler::ProcessGetUpdatesResponse(
}
}

// Auto-create permanent folder for the type if the progress marker
// changes from empty to non-empty.
if (!IsTypeWithServerGeneratedRoot(type_) &&
dir_->HasEmptyDownloadProgress(type_) &&
IsValidProgressMarker(progress_marker)) {
CreateTypeRoot(&trans);
}

UpdateSyncEntities(&trans, applicable_updates, status);

if (IsValidProgressMarker(progress_marker)) {
ExpireEntriesIfNeeded(&trans, progress_marker);
UpdateProgressMarker(progress_marker);
}

debug_info_emitter_->EmitUpdateCountersUpdate();
return SYNCER_OK;
}

void DirectoryUpdateHandler::CreateTypeRoot(
syncable::ModelNeutralWriteTransaction* trans) {
syncable::ModelNeutralMutableEntry entry(
trans, syncable::CREATE_NEW_TYPE_ROOT, type_);
if (!entry.good()) {
// This will fail only if matching entry already exists, for example
// if the type gets disabled and its progress marker gets cleared,
// then the type gets re-enabled again.
DVLOG(1) << "Type root folder " << ModelTypeToRootTag(type_)
<< " already exists.";
return;
}

entry.PutServerIsDir(true);
entry.PutUniqueServerTag(ModelTypeToRootTag(type_));
}

void DirectoryUpdateHandler::ApplyUpdates(sessions::StatusController* status) {
if (!IsApplyUpdatesRequired()) {
return;
Expand Down Expand Up @@ -205,6 +232,9 @@ void DirectoryUpdateHandler::UpdateSyncEntities(

bool DirectoryUpdateHandler::IsValidProgressMarker(
const sync_pb::DataTypeProgressMarker& progress_marker) const {
if (progress_marker.token().empty()) {
return false;
}
int field_number = progress_marker.data_type_id();
ModelType model_type = GetModelTypeFromSpecificsFieldNumber(field_number);
if (!IsRealDataType(model_type) || type_ != model_type) {
Expand Down
3 changes: 3 additions & 0 deletions sync/engine/directory_update_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class SYNC_EXPORT_PRIVATE DirectoryUpdateHandler : public UpdateHandler {
// Skips all checks and goes straight to applying the updates.
SyncerError ApplyUpdatesImpl(sessions::StatusController* status);

// Creates root node for the handled model type.
void CreateTypeRoot(syncable::ModelNeutralWriteTransaction* trans);

syncable::Directory* dir_;
ModelType type_;
scoped_refptr<ModelSafeWorker> worker_;
Expand Down
55 changes: 17 additions & 38 deletions sync/engine/directory_update_handler_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ class DirectoryUpdateHandlerProcessUpdateTest : public ::testing::Test {
return e.good() && !e.GetIsDel();
}

bool TypeRootExists(ModelType model_type) {
syncable::ReadTransaction trans(FROM_HERE, dir());
syncable::Entry e(&trans, syncable::GET_TYPE_ROOT, model_type);
return e.good() && !e.GetIsDel();
}

protected:
// Used in the construction of DirectoryTypeDebugInfoEmitters.
ObserverList<TypeDebugInfoObserver> type_observers_;
Expand Down Expand Up @@ -267,27 +273,17 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest, GarbageCollectionByVersion) {
context.set_context("context");
context.set_version(1);

scoped_ptr<sync_pb::SyncEntity> type_root =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("root")),
Id::GetRoot().GetServerId(), SYNCED_NOTIFICATIONS);
type_root->set_server_defined_unique_tag(
ModelTypeToRootTag(SYNCED_NOTIFICATIONS));
type_root->set_folder(true);

scoped_ptr<sync_pb::SyncEntity> e1 =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")),
type_root->id_string(),
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")), "",
SYNCED_NOTIFICATIONS);

scoped_ptr<sync_pb::SyncEntity> e2 =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e2")),
type_root->id_string(),
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e2")), "",
SYNCED_NOTIFICATIONS);
e2->set_version(kDefaultVersion + 100);

// Add to the applicable updates list.
SyncEntityList updates;
updates.push_back(type_root.get());
updates.push_back(e1.get());
updates.push_back(e2.get());

Expand All @@ -298,7 +294,7 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest, GarbageCollectionByVersion) {
handler.ApplyUpdates(&status);

// Verify none is deleted because they are unapplied during GC.
EXPECT_TRUE(EntryExists(type_root->id_string()));
EXPECT_TRUE(TypeRootExists(SYNCED_NOTIFICATIONS));
EXPECT_TRUE(EntryExists(e1->id_string()));
EXPECT_TRUE(EntryExists(e2->id_string()));

Expand All @@ -308,7 +304,6 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest, GarbageCollectionByVersion) {
handler.ProcessGetUpdatesResponse(
progress, context, SyncEntityList(), &status));
handler.ApplyUpdates(&status);
EXPECT_TRUE(EntryExists(type_root->id_string()));
EXPECT_FALSE(EntryExists(e1->id_string()));
EXPECT_TRUE(EntryExists(e2->id_string()));
}
Expand All @@ -330,19 +325,11 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ContextVersion) {
old_context.set_context("data");
old_context.set_data_type_id(field_number);

scoped_ptr<sync_pb::SyncEntity> type_root =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("root")),
Id::GetRoot().GetServerId(), SYNCED_NOTIFICATIONS);
type_root->set_server_defined_unique_tag(
ModelTypeToRootTag(SYNCED_NOTIFICATIONS));
type_root->set_folder(true);
scoped_ptr<sync_pb::SyncEntity> e1 =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")),
type_root->id_string(),
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")), "",
SYNCED_NOTIFICATIONS);

SyncEntityList updates;
updates.push_back(type_root.get());
updates.push_back(e1.get());

// The first response should be processed fine.
Expand All @@ -351,7 +338,9 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ContextVersion) {
progress, old_context, updates, &status));
handler.ApplyUpdates(&status);

EXPECT_TRUE(EntryExists(type_root->id_string()));
// The PREFERENCES root should be auto-created.
EXPECT_TRUE(TypeRootExists(SYNCED_NOTIFICATIONS));

EXPECT_TRUE(EntryExists(e1->id_string()));

{
Expand All @@ -368,8 +357,7 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ContextVersion) {
new_context.set_data_type_id(field_number);

scoped_ptr<sync_pb::SyncEntity> e2 =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e2")),
type_root->id_string(),
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e2")), "",
SYNCED_NOTIFICATIONS);
updates.clear();
updates.push_back(e2.get());
Expand Down Expand Up @@ -410,21 +398,12 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest,
context.set_context("context");
context.set_version(1);

scoped_ptr<sync_pb::SyncEntity> type_root =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("root")),
Id::GetRoot().GetServerId(), ARTICLES);
type_root->set_server_defined_unique_tag(ModelTypeToRootTag(ARTICLES));
type_root->set_folder(true);

scoped_ptr<sync_pb::SyncEntity> e1 =
CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")),
type_root->id_string(),
ARTICLES);
scoped_ptr<sync_pb::SyncEntity> e1 = CreateUpdate(
SyncableIdToProto(Id::CreateFromServerId("e1")), "", ARTICLES);
sync_pb::AttachmentIdProto* attachment_id = e1->add_attachment_id();
*attachment_id = CreateAttachmentIdProto();

SyncEntityList updates;
updates.push_back(type_root.get());
updates.push_back(e1.get());

// Process and apply updates.
Expand All @@ -433,7 +412,7 @@ TEST_F(DirectoryUpdateHandlerProcessUpdateTest,
handler.ProcessGetUpdatesResponse(progress, context, updates, &status));
handler.ApplyUpdates(&status);

ASSERT_TRUE(EntryExists(type_root->id_string()));
ASSERT_TRUE(TypeRootExists(ARTICLES));
ASSERT_TRUE(EntryExists(e1->id_string()));
{
syncable::ReadTransaction trans(FROM_HERE, dir());
Expand Down
Loading

0 comments on commit 8d4046a

Please sign in to comment.