| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/sync/model/processor_entity.h" |
| |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/not_fatal_until.h" |
| #include "base/trace_event/memory_usage_estimator.h" |
| #include "components/sync/base/client_tag_hash.h" |
| #include "components/sync/base/deletion_origin.h" |
| #include "components/sync/base/time.h" |
| #include "components/sync/engine/commit_and_get_updates_types.h" |
| #include "components/sync/protocol/collaboration_metadata.h" |
| #include "components/sync/protocol/entity_data.h" |
| #include "components/sync/protocol/entity_specifics.pb.h" |
| #include "components/sync/protocol/unique_position.pb.h" |
| #include "components/version_info/version_info.h" |
| |
| namespace syncer { |
| |
| std::unique_ptr<ProcessorEntity> ProcessorEntity::CreateNew( |
| const std::string& storage_key, |
| const ClientTagHash& client_tag_hash, |
| const std::string& server_id, |
| base::Time creation_time) { |
| return base::WrapUnique(new ProcessorEntity( |
| storage_key, ProcessorEntityMetadata::CreateNew( |
| client_tag_hash, server_id, creation_time))); |
| } |
| |
| std::unique_ptr<ProcessorEntity> ProcessorEntity::CreateFromMetadata( |
| const std::string& storage_key, |
| sync_pb::EntityMetadata metadata) { |
| DCHECK(!storage_key.empty()); |
| std::unique_ptr<ProcessorEntityMetadata> entity_metadata = |
| ProcessorEntityMetadata::FromProto(std::move(metadata)); |
| if (!entity_metadata) { |
| return nullptr; |
| } |
| return base::WrapUnique( |
| new ProcessorEntity(storage_key, std::move(*entity_metadata))); |
| } |
| |
| ProcessorEntity::ProcessorEntity(const std::string& storage_key, |
| ProcessorEntityMetadata metadata) |
| : storage_key_(storage_key), |
| metadata_(std::move(metadata)), |
| commit_requested_sequence_number_( |
| metadata_.proto().acked_sequence_number()) {} |
| |
| ProcessorEntity::~ProcessorEntity() = default; |
| |
| ClientTagHash ProcessorEntity::GetClientTagHash() const { |
| return metadata_.GetClientTagHash(); |
| } |
| |
| void ProcessorEntity::SetStorageKey(const std::string& storage_key) { |
| DCHECK(storage_key_.empty()); |
| DCHECK(!storage_key.empty()); |
| storage_key_ = storage_key; |
| } |
| |
| void ProcessorEntity::ClearStorageKey() { |
| storage_key_.clear(); |
| } |
| |
| void ProcessorEntity::SetCommitData(std::unique_ptr<EntityData> data) { |
| DCHECK(data); |
| // Update data's fields from metadata. |
| data->client_tag_hash = |
| ClientTagHash::FromHashed(metadata().client_tag_hash()); |
| if (!metadata().server_id().empty()) { |
| data->id = metadata().server_id(); |
| } |
| data->creation_time = ProtoTimeToTime(metadata().creation_time()); |
| data->modification_time = ProtoTimeToTime(metadata().modification_time()); |
| |
| commit_data_ = std::move(data); |
| // TODO(crbug.com/408182457): This DCHECK is sometimes violated for SESSIONS. |
| DCHECK(HasCommitData()); |
| } |
| |
| bool ProcessorEntity::HasCommitData() const { |
| return commit_data_ && !commit_data_->client_tag_hash.value().empty(); |
| } |
| |
| bool ProcessorEntity::MatchesData(const EntityData& data) const { |
| return metadata_.MatchesData(data); |
| } |
| |
| bool ProcessorEntity::MatchesOwnBaseData() const { |
| return metadata_.MatchesOwnBaseData(); |
| } |
| |
| bool ProcessorEntity::MatchesBaseData(const EntityData& data) const { |
| return metadata_.MatchesBaseData(data); |
| } |
| |
| bool ProcessorEntity::IsUnsynced() const { |
| return metadata_.IsUnsynced(); |
| } |
| |
| bool ProcessorEntity::IsUnsyncedLocalCreation() const { |
| return metadata_.IsUnsyncedLocalCreation(); |
| } |
| |
| bool ProcessorEntity::RequiresCommitRequest() const { |
| return metadata().sequence_number() > commit_requested_sequence_number_; |
| } |
| |
| bool ProcessorEntity::RequiresCommitData() const { |
| return RequiresCommitRequest() && !HasCommitData() && |
| !metadata().is_deleted(); |
| } |
| |
| bool ProcessorEntity::CanClearMetadata() const { |
| return metadata().is_deleted() && !IsUnsynced(); |
| } |
| |
| bool ProcessorEntity::IsVersionAlreadyKnown(int64_t update_version) const { |
| return metadata_.IsVersionAlreadyKnown(update_version); |
| } |
| |
| void ProcessorEntity::UpdateCommitDataAndSequenceAfterRemoteUpdate() { |
| // Either these already matched, acked was just bumped to squash a pending |
| // commit and this should follow, or the pending commit needs to be requeued. |
| commit_requested_sequence_number_ = metadata().acked_sequence_number(); |
| // If local change was made while server assigned a new id to the entity, |
| // update id in cached commit data. |
| if (HasCommitData() && commit_data_->id != metadata().server_id()) { |
| DCHECK(commit_data_->id.empty()); |
| commit_data_->id = metadata().server_id(); |
| } |
| } |
| |
| void ProcessorEntity::RecordIgnoredRemoteUpdate( |
| const UpdateResponseData& update) { |
| DCHECK(metadata().server_id().empty() || |
| metadata().server_id() == update.entity.id); |
| metadata_.RecordIgnoredRemoteUpdate(update); |
| UpdateCommitDataAndSequenceAfterRemoteUpdate(); |
| } |
| |
| void ProcessorEntity::RecordAcceptedRemoteUpdate( |
| const UpdateResponseData& update, |
| sync_pb::EntitySpecifics trimmed_specifics, |
| std::optional<sync_pb::UniquePosition> unique_position) { |
| DCHECK(!IsUnsynced()); |
| metadata_.RecordAcceptedRemoteUpdate(update, std::move(trimmed_specifics), |
| std::move(unique_position)); |
| UpdateCommitDataAndSequenceAfterRemoteUpdate(); |
| } |
| |
| void ProcessorEntity::RecordForcedRemoteUpdate( |
| const UpdateResponseData& update, |
| sync_pb::EntitySpecifics trimmed_specifics, |
| std::optional<sync_pb::UniquePosition> unique_position) { |
| CHECK(IsUnsynced(), base::NotFatalUntil::M141); |
| commit_data_.reset(); |
| metadata_.RecordForcedRemoteUpdate(update, std::move(trimmed_specifics), |
| std::move(unique_position)); |
| UpdateCommitDataAndSequenceAfterRemoteUpdate(); |
| } |
| |
| void ProcessorEntity::OverrideServerMetadata(const std::string& server_id, |
| int64_t server_version) { |
| metadata_.OverrideServerMetadata(server_id, server_version); |
| if (commit_data_) { |
| commit_data_->id = server_id; |
| } |
| } |
| |
| void ProcessorEntity::RecordLocalUpdate( |
| std::unique_ptr<EntityData> data, |
| sync_pb::EntitySpecifics trimmed_specifics, |
| std::optional<sync_pb::UniquePosition> unique_position) { |
| DCHECK(!metadata().client_tag_hash().empty()); |
| metadata_.RecordLocalUpdate(*data, std::move(trimmed_specifics), |
| unique_position); |
| SetCommitData(std::move(data)); |
| } |
| |
| bool ProcessorEntity::RecordLocalDeletion(const DeletionOrigin& origin) { |
| commit_data_.reset(); |
| metadata_.RecordLocalDeletion(origin); |
| // Return true if server might know about this entity. |
| // TODO(crbug.com/41329567): This check will prevent sending tombstone in |
| // situation when it should have been sent under following conditions: |
| // - Original entity was committed to server, but client crashed before |
| // receiving response. |
| // - Entity was deleted while client was offline. |
| // Correct behavior is to send tombstone anyway, but the legacy Directory |
| // implementation doesn't and it is unclear how server will react to such |
| // tombstones. Change the behavior to always sending tombstone after |
| // experimenting with server. |
| return (metadata_.proto().server_version() != kUncommittedVersion) || |
| (commit_requested_sequence_number_ > |
| metadata_.proto().acked_sequence_number()); |
| } |
| |
| void ProcessorEntity::InitializeCommitRequestData(CommitRequestData* request) { |
| if (!metadata().is_deleted()) { |
| DCHECK(HasCommitData()); |
| DCHECK_EQ(commit_data_->client_tag_hash.value(), |
| metadata().client_tag_hash()); |
| DCHECK_EQ(commit_data_->id, metadata().server_id()); |
| request->entity = std::move(commit_data_); |
| } else { |
| // Make an EntityData with empty specifics to indicate deletion. This is |
| // done lazily here to simplify loading a pending deletion on startup. |
| auto data = std::make_unique<syncer::EntityData>(); |
| data->client_tag_hash = |
| ClientTagHash::FromHashed(metadata().client_tag_hash()); |
| data->id = metadata().server_id(); |
| data->creation_time = ProtoTimeToTime(metadata().creation_time()); |
| data->modification_time = ProtoTimeToTime(metadata().modification_time()); |
| if (metadata().has_deletion_origin()) { |
| data->deletion_origin = metadata().deletion_origin(); |
| } |
| if (metadata().has_collaboration()) { |
| data->collaboration_metadata = |
| CollaborationMetadata::FromLocalProto(metadata().collaboration()); |
| } |
| request->entity = std::move(data); |
| } |
| |
| request->sequence_number = metadata().sequence_number(); |
| request->base_version = metadata().server_version(); |
| request->specifics_hash = metadata().specifics_hash(); |
| commit_requested_sequence_number_ = metadata().sequence_number(); |
| } |
| |
| void ProcessorEntity::ReceiveCommitResponse(const CommitResponseData& data, |
| bool commit_only) { |
| CHECK_EQ(metadata().client_tag_hash(), data.client_tag_hash.value(), |
| base::NotFatalUntil::M141); |
| CHECK_GT(data.sequence_number, metadata().acked_sequence_number(), |
| base::NotFatalUntil::M141); |
| // Version is not valid for commit only types, as it's stripped before being |
| // sent to the server, so it cannot behave correctly. |
| // Ignore the response if the server responds with an unexpected version. |
| if (!commit_only && data.response_version <= metadata().server_version()) { |
| return; |
| } |
| |
| // The server can assign us a new ID in a commit response. |
| metadata_.RecordCommitResponse(data); |
| if (!IsUnsynced()) { |
| // Clear pending commit data if there hasn't been another commit request |
| // since the one that is currently getting acked. |
| commit_data_.reset(); |
| } else { |
| // If local change was made while server assigned a new id to the entity, |
| // update id in cached commit data. |
| if (HasCommitData() && commit_data_->id != metadata().server_id()) { |
| commit_data_->id = metadata().server_id(); |
| } |
| } |
| } |
| |
| void ProcessorEntity::ClearTransientSyncState() { |
| // If we have any unacknowledged commit requests outstanding, they've been |
| // dropped and we should forget about them. |
| commit_requested_sequence_number_ = metadata().acked_sequence_number(); |
| } |
| |
| void ProcessorEntity::IncrementSequenceNumber(base::Time modification_time) { |
| metadata_.IncrementSequenceNumber(); |
| } |
| |
| size_t ProcessorEntity::EstimateMemoryUsage() const { |
| using base::trace_event::EstimateMemoryUsage; |
| size_t memory_usage = metadata_.EstimateMemoryUsage(); |
| memory_usage += EstimateMemoryUsage(storage_key_); |
| memory_usage += EstimateMemoryUsage(commit_data_); |
| return memory_usage; |
| } |
| |
| |
| } // namespace syncer |