| Index: sync/engine/commit_util.cc
|
| diff --git a/sync/engine/process_commit_response_command.cc b/sync/engine/commit_util.cc
|
| similarity index 56%
|
| rename from sync/engine/process_commit_response_command.cc
|
| rename to sync/engine/commit_util.cc
|
| index 97f403f795f7f8750e0b77625cb489ee8b120135..dc6991c6690702f9ae05625eca61b87b423a3605 100644
|
| --- a/sync/engine/process_commit_response_command.cc
|
| +++ b/sync/engine/commit_util.cc
|
| @@ -2,222 +2,194 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -#include "sync/engine/process_commit_response_command.h"
|
| +#include "sync/engine/commit_util.h"
|
|
|
| -#include <cstddef>
|
| +#include <limits>
|
| #include <set>
|
| #include <string>
|
| #include <vector>
|
|
|
| -#include "base/basictypes.h"
|
| -#include "base/location.h"
|
| +#include "base/strings/string_util.h"
|
| #include "sync/engine/syncer_proto_util.h"
|
| -#include "sync/engine/syncer_util.h"
|
| #include "sync/internal_api/public/base/unique_position.h"
|
| +#include "sync/protocol/bookmark_specifics.pb.h"
|
| +#include "sync/protocol/sync.pb.h"
|
| #include "sync/sessions/sync_session.h"
|
| +#include "sync/syncable/directory.h"
|
| #include "sync/syncable/entry.h"
|
| #include "sync/syncable/model_neutral_mutable_entry.h"
|
| -#include "sync/syncable/syncable_model_neutral_write_transaction.h"
|
| +#include "sync/syncable/syncable_base_transaction.h"
|
| +#include "sync/syncable/syncable_base_write_transaction.h"
|
| +#include "sync/syncable/syncable_changes_version.h"
|
| #include "sync/syncable/syncable_proto_util.h"
|
| -#include "sync/syncable/syncable_read_transaction.h"
|
| #include "sync/syncable/syncable_util.h"
|
| #include "sync/util/time.h"
|
|
|
| using std::set;
|
| using std::string;
|
| using std::vector;
|
| -using sync_pb::CommitResponse;
|
|
|
| namespace syncer {
|
|
|
| -using sessions::OrderedCommitSet;
|
| -using sessions::StatusController;
|
| using sessions::SyncSession;
|
| -using syncable::ModelNeutralWriteTransaction;
|
| -using syncable::ModelNeutralMutableEntry;
|
| using syncable::Entry;
|
| -using syncable::BASE_VERSION;
|
| -using syncable::GET_BY_ID;
|
| -using syncable::GET_BY_HANDLE;
|
| -using syncable::ID;
|
| using syncable::IS_DEL;
|
| -using syncable::IS_DIR;
|
| using syncable::IS_UNAPPLIED_UPDATE;
|
| using syncable::IS_UNSYNCED;
|
| -using syncable::PARENT_ID;
|
| -using syncable::SERVER_IS_DEL;
|
| -using syncable::SERVER_PARENT_ID;
|
| -using syncable::SERVER_VERSION;
|
| -using syncable::SYNCER;
|
| -using syncable::SYNCING;
|
| -
|
| -ProcessCommitResponseCommand::ProcessCommitResponseCommand(
|
| - const sessions::OrderedCommitSet& commit_set,
|
| - const sync_pb::ClientToServerMessage& commit_message,
|
| - const sync_pb::ClientToServerResponse& commit_response)
|
| - : commit_set_(commit_set),
|
| - commit_message_(commit_message),
|
| - commit_response_(commit_response) {
|
| -}
|
| -
|
| -ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {}
|
| -
|
| -SyncerError ProcessCommitResponseCommand::ExecuteImpl(SyncSession* session) {
|
| - syncable::Directory* dir = session->context()->directory();
|
| - StatusController* status = session->mutable_status_controller();
|
| - const CommitResponse& cr = commit_response_.commit();
|
| - const sync_pb::CommitMessage& commit_message = commit_message_.commit();
|
| -
|
| - int transient_error_commits = 0;
|
| - int conflicting_commits = 0;
|
| - int error_commits = 0;
|
| - int successes = 0;
|
| -
|
| - set<syncable::Id> deleted_folders;
|
| -
|
| - { // Scope for ModelNeutralWriteTransaction.
|
| - ModelNeutralWriteTransaction trans(FROM_HERE, SYNCER, dir);
|
| - for (size_t i = 0; i < commit_set_.Size(); i++) {
|
| - CommitResponse::ResponseType response_type = ProcessSingleCommitResponse(
|
| - &trans,
|
| - cr.entryresponse(i),
|
| - commit_message.entries(i),
|
| - commit_set_.GetCommitHandleAt(i),
|
| - &deleted_folders);
|
| - switch (response_type) {
|
| - case CommitResponse::INVALID_MESSAGE:
|
| - ++error_commits;
|
| - break;
|
| - case CommitResponse::CONFLICT:
|
| - ++conflicting_commits;
|
| - status->increment_num_server_conflicts();
|
| - break;
|
| - case CommitResponse::SUCCESS:
|
| - // TODO(sync): worry about sync_rate_ rate calc?
|
| - ++successes;
|
| - if (commit_set_.GetModelTypeAt(i) == BOOKMARKS)
|
| - status->increment_num_successful_bookmark_commits();
|
| - status->increment_num_successful_commits();
|
| - break;
|
| - case CommitResponse::OVER_QUOTA:
|
| - // We handle over quota like a retry, which is same as transient.
|
| - case CommitResponse::RETRY:
|
| - case CommitResponse::TRANSIENT_ERROR:
|
| - ++transient_error_commits;
|
| - break;
|
| - default:
|
| - LOG(FATAL) << "Bad return from ProcessSingleCommitResponse";
|
| - }
|
| - }
|
| -
|
| - MarkDeletedChildrenSynced(dir, &trans, &deleted_folders);
|
| +using syncable::Id;
|
| +using syncable::SPECIFICS;
|
| +using syncable::UNIQUE_POSITION;
|
| +
|
| +namespace commit_util {
|
| +
|
| +void AddExtensionsActivityToMessage(
|
| + ExtensionsActivity* activity,
|
| + ExtensionsActivity::Records* extensions_activity_buffer,
|
| + sync_pb::CommitMessage* message) {
|
| + // This isn't perfect, since the set of extensions activity may not correlate
|
| + // exactly with the items being committed. That's OK as long as we're looking
|
| + // for a rough estimate of extensions activity, not an precise mapping of
|
| + // which commits were triggered by which extension.
|
| + //
|
| + // We will push this list of extensions activity back into the
|
| + // ExtensionsActivityMonitor if this commit fails. That's why we must keep a
|
| + // copy of these records in the session.
|
| + activity->GetAndClearRecords(extensions_activity_buffer);
|
| +
|
| + const ExtensionsActivity::Records& records = *extensions_activity_buffer;
|
| + for (ExtensionsActivity::Records::const_iterator it =
|
| + records.begin();
|
| + it != records.end(); ++it) {
|
| + sync_pb::ChromiumExtensionsActivity* activity_message =
|
| + message->add_extensions_activity();
|
| + activity_message->set_extension_id(it->second.extension_id);
|
| + activity_message->set_bookmark_writes_since_last_commit(
|
| + it->second.bookmark_write_count);
|
| }
|
| +}
|
|
|
| - int commit_count = static_cast<int>(commit_set_.Size());
|
| - if (commit_count == successes) {
|
| - return SYNCER_OK;
|
| - } else if (error_commits > 0) {
|
| - return SERVER_RETURN_UNKNOWN_ERROR;
|
| - } else if (transient_error_commits > 0) {
|
| - return SERVER_RETURN_TRANSIENT_ERROR;
|
| - } else if (conflicting_commits > 0) {
|
| - // This means that the server already has an item with this version, but
|
| - // we haven't seen that update yet.
|
| - //
|
| - // A well-behaved client should respond to this by proceeding to the
|
| - // download updates phase, fetching the conflicting items, then attempting
|
| - // to resolve the conflict. That's not what this client does.
|
| - //
|
| - // We don't currently have any code to support that exceptional control
|
| - // flow. Instead, we abort the current sync cycle and start a new one. The
|
| - // end result is the same.
|
| - return SERVER_RETURN_CONFLICT;
|
| - } else {
|
| - LOG(FATAL) << "Inconsistent counts when processing commit response";
|
| - return SYNCER_OK;
|
| +void AddClientConfigParamsToMessage(
|
| + ModelTypeSet enabled_types,
|
| + sync_pb::CommitMessage* message) {
|
| + sync_pb::ClientConfigParams* config_params = message->mutable_config_params();
|
| + for (ModelTypeSet::Iterator it = enabled_types.First(); it.Good(); it.Inc()) {
|
| + if (ProxyTypes().Has(it.Get()))
|
| + continue;
|
| + int field_number = GetSpecificsFieldNumberFromModelType(it.Get());
|
| + config_params->mutable_enabled_type_ids()->Add(field_number);
|
| }
|
| + config_params->set_tabs_datatype_enabled(
|
| + enabled_types.Has(syncer::PROXY_TABS));
|
| }
|
|
|
| -void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) {
|
| - if (res.has_error_message())
|
| - LOG(WARNING) << " " << res.error_message();
|
| - else
|
| - LOG(WARNING) << " No detailed error message returned from server";
|
| -}
|
| +namespace {
|
| +void SetEntrySpecifics(const Entry& meta_entry,
|
| + sync_pb::SyncEntity* sync_entry) {
|
| + // Add the new style extension and the folder bit.
|
| + sync_entry->mutable_specifics()->CopyFrom(meta_entry.GetSpecifics());
|
| + sync_entry->set_folder(meta_entry.GetIsDir());
|
|
|
| -CommitResponse::ResponseType
|
| -ProcessCommitResponseCommand::ProcessSingleCommitResponse(
|
| - syncable::ModelNeutralWriteTransaction* trans,
|
| - const sync_pb::CommitResponse_EntryResponse& server_entry,
|
| - const sync_pb::SyncEntity& commit_request_entry,
|
| - const int64 metahandle,
|
| - set<syncable::Id>* deleted_folders) {
|
| - ModelNeutralMutableEntry local_entry(trans, GET_BY_HANDLE, metahandle);
|
| - CHECK(local_entry.good());
|
| - bool syncing_was_set = local_entry.GetSyncing();
|
| - local_entry.PutSyncing(false);
|
| -
|
| - CommitResponse::ResponseType response = (CommitResponse::ResponseType)
|
| - server_entry.response_type();
|
| - if (!CommitResponse::ResponseType_IsValid(response)) {
|
| - LOG(ERROR) << "Commit response has unknown response type! Possibly out "
|
| - "of date client?";
|
| - return CommitResponse::INVALID_MESSAGE;
|
| - }
|
| - if (CommitResponse::TRANSIENT_ERROR == response) {
|
| - DVLOG(1) << "Transient Error Committing: " << local_entry;
|
| - LogServerError(server_entry);
|
| - return CommitResponse::TRANSIENT_ERROR;
|
| - }
|
| - if (CommitResponse::INVALID_MESSAGE == response) {
|
| - LOG(ERROR) << "Error Commiting: " << local_entry;
|
| - LogServerError(server_entry);
|
| - return response;
|
| - }
|
| - if (CommitResponse::CONFLICT == response) {
|
| - DVLOG(1) << "Conflict Committing: " << local_entry;
|
| - return response;
|
| + CHECK(!sync_entry->specifics().password().has_client_only_encrypted_data());
|
| + DCHECK_EQ(meta_entry.GetModelType(), GetModelType(*sync_entry));
|
| +}
|
| +} // namespace
|
| +
|
| +void BuildCommitItem(
|
| + const syncable::Entry& meta_entry,
|
| + sync_pb::SyncEntity* sync_entry) {
|
| + syncable::Id id = meta_entry.GetId();
|
| + sync_entry->set_id_string(SyncableIdToProto(id));
|
| +
|
| + string name = meta_entry.GetNonUniqueName();
|
| + CHECK(!name.empty()); // Make sure this isn't an update.
|
| + // Note: Truncation is also performed in WriteNode::SetTitle(..). But this
|
| + // call is still necessary to handle any title changes that might originate
|
| + // elsewhere, or already be persisted in the directory.
|
| + TruncateUTF8ToByteSize(name, 255, &name);
|
| + sync_entry->set_name(name);
|
| +
|
| + // Set the non_unique_name. If we do, the server ignores
|
| + // the |name| value (using |non_unique_name| instead), and will return
|
| + // in the CommitResponse a unique name if one is generated.
|
| + // We send both because it may aid in logging.
|
| + sync_entry->set_non_unique_name(name);
|
| +
|
| + if (!meta_entry.GetUniqueClientTag().empty()) {
|
| + sync_entry->set_client_defined_unique_tag(
|
| + meta_entry.GetUniqueClientTag());
|
| }
|
| - if (CommitResponse::RETRY == response) {
|
| - DVLOG(1) << "Retry Committing: " << local_entry;
|
| - return response;
|
| +
|
| + // Deleted items with server-unknown parent ids can be a problem so we set
|
| + // the parent to 0. (TODO(sync): Still true in protocol?).
|
| + Id new_parent_id;
|
| + if (meta_entry.GetIsDel() &&
|
| + !meta_entry.GetParentId().ServerKnows()) {
|
| + new_parent_id = syncable::BaseTransaction::root_id();
|
| + } else {
|
| + new_parent_id = meta_entry.GetParentId();
|
| }
|
| - if (CommitResponse::OVER_QUOTA == response) {
|
| - LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
|
| - return response;
|
| + sync_entry->set_parent_id_string(SyncableIdToProto(new_parent_id));
|
| +
|
| + // If our parent has changed, send up the old one so the server
|
| + // can correctly deal with multiple parents.
|
| + // TODO(nick): With the server keeping track of the primary sync parent,
|
| + // it should not be necessary to provide the old_parent_id: the version
|
| + // number should suffice.
|
| + if (new_parent_id != meta_entry.GetServerParentId() &&
|
| + 0 != meta_entry.GetBaseVersion() &&
|
| + syncable::CHANGES_VERSION != meta_entry.GetBaseVersion()) {
|
| + sync_entry->set_old_parent_id(
|
| + SyncableIdToProto(meta_entry.GetServerParentId()));
|
| }
|
| - if (!server_entry.has_id_string()) {
|
| - LOG(ERROR) << "Commit response has no id";
|
| - return CommitResponse::INVALID_MESSAGE;
|
| +
|
| + int64 version = meta_entry.GetBaseVersion();
|
| + if (syncable::CHANGES_VERSION == version || 0 == version) {
|
| + // Undeletions are only supported for items that have a client tag.
|
| + DCHECK(!id.ServerKnows() ||
|
| + !meta_entry.GetUniqueClientTag().empty())
|
| + << meta_entry;
|
| +
|
| + // Version 0 means to create or undelete an object.
|
| + sync_entry->set_version(0);
|
| + } else {
|
| + DCHECK(id.ServerKnows()) << meta_entry;
|
| + sync_entry->set_version(meta_entry.GetBaseVersion());
|
| }
|
| + sync_entry->set_ctime(TimeToProtoTime(meta_entry.GetCtime()));
|
| + sync_entry->set_mtime(TimeToProtoTime(meta_entry.GetMtime()));
|
|
|
| - // Implied by the IsValid call above, but here for clarity.
|
| - DCHECK_EQ(CommitResponse::SUCCESS, response) << response;
|
| - // Check to see if we've been given the ID of an existing entry. If so treat
|
| - // it as an error response and retry later.
|
| - const syncable::Id& server_entry_id =
|
| - SyncableIdFromProto(server_entry.id_string());
|
| - if (local_entry.GetId() != server_entry_id) {
|
| - Entry e(trans, GET_BY_ID, server_entry_id);
|
| - if (e.good()) {
|
| - LOG(ERROR)
|
| - << "Got duplicate id when commiting id: "
|
| - << local_entry.GetId()
|
| - << ". Treating as an error return";
|
| - return CommitResponse::INVALID_MESSAGE;
|
| + // Deletion is final on the server, let's move things and then delete them.
|
| + if (meta_entry.GetIsDel()) {
|
| + sync_entry->set_deleted(true);
|
| + } else {
|
| + if (meta_entry.GetSpecifics().has_bookmark()) {
|
| + // Both insert_after_item_id and position_in_parent fields are set only
|
| + // for legacy reasons. See comments in sync.proto for more information.
|
| + const Id& prev_id = meta_entry.GetPredecessorId();
|
| + string prev_id_string =
|
| + prev_id.IsRoot() ? string() : prev_id.GetServerId();
|
| + sync_entry->set_insert_after_item_id(prev_id_string);
|
| + sync_entry->set_position_in_parent(
|
| + meta_entry.GetUniquePosition().ToInt64());
|
| + meta_entry.GetUniquePosition().ToProto(
|
| + sync_entry->mutable_unique_position());
|
| }
|
| + SetEntrySpecifics(meta_entry, sync_entry);
|
| }
|
| +}
|
|
|
| - if (server_entry.version() == 0) {
|
| - LOG(WARNING) << "Server returned a zero version on a commit response.";
|
| - }
|
|
|
| - ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
|
| - local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders);
|
| - return response;
|
| +// Helpers for ProcessSingleCommitResponse.
|
| +namespace {
|
| +
|
| +void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) {
|
| + if (res.has_error_message())
|
| + LOG(WARNING) << " " << res.error_message();
|
| + else
|
| + LOG(WARNING) << " No detailed error message returned from server";
|
| }
|
|
|
| -const string& ProcessCommitResponseCommand::GetResultingPostCommitName(
|
| +const string& GetResultingPostCommitName(
|
| const sync_pb::SyncEntity& committed_entry,
|
| const sync_pb::CommitResponse_EntryResponse& entry_response) {
|
| const string& response_name =
|
| @@ -227,7 +199,7 @@ const string& ProcessCommitResponseCommand::GetResultingPostCommitName(
|
| return SyncerProtoUtil::NameFromSyncEntity(committed_entry);
|
| }
|
|
|
| -bool ProcessCommitResponseCommand::UpdateVersionAfterCommit(
|
| +bool UpdateVersionAfterCommit(
|
| const sync_pb::SyncEntity& committed_entry,
|
| const sync_pb::CommitResponse_EntryResponse& entry_response,
|
| const syncable::Id& pre_commit_id,
|
| @@ -264,7 +236,7 @@ bool ProcessCommitResponseCommand::UpdateVersionAfterCommit(
|
| return true;
|
| }
|
|
|
| -bool ProcessCommitResponseCommand::ChangeIdAfterCommit(
|
| +bool ChangeIdAfterCommit(
|
| const sync_pb::CommitResponse_EntryResponse& entry_response,
|
| const syncable::Id& pre_commit_id,
|
| syncable::ModelNeutralMutableEntry* local_entry) {
|
| @@ -278,7 +250,10 @@ bool ProcessCommitResponseCommand::ChangeIdAfterCommit(
|
| DVLOG(1) << " ID changed while committing an old entry. "
|
| << pre_commit_id << " became " << entry_response_id << ".";
|
| }
|
| - ModelNeutralMutableEntry same_id(trans, GET_BY_ID, entry_response_id);
|
| + syncable::ModelNeutralMutableEntry same_id(
|
| + trans,
|
| + syncable::GET_BY_ID,
|
| + entry_response_id);
|
| // We should trap this before this function.
|
| if (same_id.good()) {
|
| LOG(ERROR) << "ID clash with id " << entry_response_id
|
| @@ -291,7 +266,7 @@ bool ProcessCommitResponseCommand::ChangeIdAfterCommit(
|
| return true;
|
| }
|
|
|
| -void ProcessCommitResponseCommand::UpdateServerFieldsAfterCommit(
|
| +void UpdateServerFieldsAfterCommit(
|
| const sync_pb::SyncEntity& committed_entry,
|
| const sync_pb::CommitResponse_EntryResponse& entry_response,
|
| syncable::ModelNeutralMutableEntry* local_entry) {
|
| @@ -342,7 +317,7 @@ void ProcessCommitResponseCommand::UpdateServerFieldsAfterCommit(
|
| }
|
| }
|
|
|
| -void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse(
|
| +void ProcessSuccessfulCommitResponse(
|
| const sync_pb::SyncEntity& committed_entry,
|
| const sync_pb::CommitResponse_EntryResponse& entry_response,
|
| const syncable::Id& pre_commit_id,
|
| @@ -384,4 +359,82 @@ void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse(
|
| }
|
| }
|
|
|
| +} // namespace
|
| +
|
| +sync_pb::CommitResponse::ResponseType
|
| +ProcessSingleCommitResponse(
|
| + syncable::BaseWriteTransaction* trans,
|
| + const sync_pb::CommitResponse_EntryResponse& server_entry,
|
| + const sync_pb::SyncEntity& commit_request_entry,
|
| + int64 metahandle,
|
| + set<syncable::Id>* deleted_folders) {
|
| + syncable::ModelNeutralMutableEntry local_entry(
|
| + trans,
|
| + syncable::GET_BY_HANDLE,
|
| + metahandle);
|
| + CHECK(local_entry.good());
|
| + bool syncing_was_set = local_entry.GetSyncing();
|
| + local_entry.PutSyncing(false);
|
| +
|
| + sync_pb::CommitResponse::ResponseType response = server_entry.response_type();
|
| + if (!sync_pb::CommitResponse::ResponseType_IsValid(response)) {
|
| + LOG(ERROR) << "Commit response has unknown response type! Possibly out "
|
| + "of date client?";
|
| + return sync_pb::CommitResponse::INVALID_MESSAGE;
|
| + }
|
| + if (sync_pb::CommitResponse::TRANSIENT_ERROR == response) {
|
| + DVLOG(1) << "Transient Error Committing: " << local_entry;
|
| + LogServerError(server_entry);
|
| + return sync_pb::CommitResponse::TRANSIENT_ERROR;
|
| + }
|
| + if (sync_pb::CommitResponse::INVALID_MESSAGE == response) {
|
| + LOG(ERROR) << "Error Commiting: " << local_entry;
|
| + LogServerError(server_entry);
|
| + return response;
|
| + }
|
| + if (sync_pb::CommitResponse::CONFLICT == response) {
|
| + DVLOG(1) << "Conflict Committing: " << local_entry;
|
| + return response;
|
| + }
|
| + if (sync_pb::CommitResponse::RETRY == response) {
|
| + DVLOG(1) << "Retry Committing: " << local_entry;
|
| + return response;
|
| + }
|
| + if (sync_pb::CommitResponse::OVER_QUOTA == response) {
|
| + LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
|
| + return response;
|
| + }
|
| + if (!server_entry.has_id_string()) {
|
| + LOG(ERROR) << "Commit response has no id";
|
| + return sync_pb::CommitResponse::INVALID_MESSAGE;
|
| + }
|
| +
|
| + // Implied by the IsValid call above, but here for clarity.
|
| + DCHECK_EQ(sync_pb::CommitResponse::SUCCESS, response) << response;
|
| + // Check to see if we've been given the ID of an existing entry. If so treat
|
| + // it as an error response and retry later.
|
| + const syncable::Id& server_entry_id =
|
| + SyncableIdFromProto(server_entry.id_string());
|
| + if (local_entry.GetId() != server_entry_id) {
|
| + Entry e(trans, syncable::GET_BY_ID, server_entry_id);
|
| + if (e.good()) {
|
| + LOG(ERROR)
|
| + << "Got duplicate id when commiting id: "
|
| + << local_entry.GetId()
|
| + << ". Treating as an error return";
|
| + return sync_pb::CommitResponse::INVALID_MESSAGE;
|
| + }
|
| + }
|
| +
|
| + if (server_entry.version() == 0) {
|
| + LOG(WARNING) << "Server returned a zero version on a commit response.";
|
| + }
|
| +
|
| + ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
|
| + local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders);
|
| + return response;
|
| +}
|
| +
|
| +} // namespace commit_util
|
| +
|
| } // namespace syncer
|
|
|