blob: cc78a5239af194a46f3344c47925254885729bf0 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/nigori/nigori_model_type_processor.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/sync/base/time.h"
#include "components/sync/engine/commit_queue.h"
#include "components/sync/engine/model_type_processor_proxy.h"
#include "components/sync/model_impl/processor_entity.h"
#include "components/sync/nigori/nigori_sync_bridge.h"
namespace syncer {
namespace {
// TODO(mamir): remove those and adjust the code accordingly. Similarly in
// tests.
const char kNigoriStorageKey[] = "NigoriStorageKey";
const char kNigoriClientTagHash[] = "NigoriClientTagHash";
} // namespace
NigoriModelTypeProcessor::NigoriModelTypeProcessor()
: bridge_(nullptr), weak_ptr_factory_for_worker_(this) {}
NigoriModelTypeProcessor::~NigoriModelTypeProcessor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void NigoriModelTypeProcessor::ConnectSync(
std::unique_ptr<CommitQueue> worker) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << "Successfully connected Encryption Keys";
worker_ = std::move(worker);
NudgeForCommitIfNeeded();
}
void NigoriModelTypeProcessor::DisconnectSync() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsConnected());
DVLOG(1) << "Disconnecting sync for Encryption Keys";
weak_ptr_factory_for_worker_.InvalidateWeakPtrs();
worker_.reset();
if (entity_) {
entity_->ClearTransientSyncState();
}
}
void NigoriModelTypeProcessor::GetLocalChanges(
size_t max_entries,
GetLocalChangesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(max_entries, 0U);
// If there is a model error, it must have been reported already but hasn't
// reached the sync engine yet. In this case return directly to avoid
// interactions with the bridge.
if (model_error_) {
std::move(callback).Run(CommitRequestDataList());
return;
}
DCHECK(entity_);
// No local changes to commit.
if (!entity_->RequiresCommitRequest()) {
std::move(callback).Run(CommitRequestDataList());
return;
}
if (entity_->RequiresCommitData()) {
// SetCommitData will update EntityData's fields with values from
// metadata.
entity_->SetCommitData(bridge_->GetData());
}
auto commit_request_data = std::make_unique<CommitRequestData>();
entity_->InitializeCommitRequestData(commit_request_data.get());
CommitRequestDataList commit_request_data_list;
commit_request_data_list.push_back(std::move(commit_request_data));
std::move(callback).Run(std::move(commit_request_data_list));
}
void NigoriModelTypeProcessor::OnCommitCompleted(
const sync_pb::ModelTypeState& type_state,
const CommitResponseDataList& response_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(entity_);
model_type_state_ = type_state;
if (!response_list.empty()) {
entity_->ReceiveCommitResponse(response_list[0], /*commit_only=*/false,
ModelType::NIGORI);
} else {
// If the entity hasn't been mentioned in response_list, then it's not
// committed and we should reset its commit_requested_sequence_number so
// they are committed again on next sync cycle.
entity_->ClearTransientSyncState();
}
// Ask the bridge to persist the new metadata.
bridge_->ApplySyncChanges(/*data=*/base::nullopt);
}
void NigoriModelTypeProcessor::OnUpdateReceived(
const sync_pb::ModelTypeState& type_state,
UpdateResponseDataList updates) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(model_ready_to_sync_);
// If there is a model error, it must have been reported already but hasn't
// reached the sync engine yet. In this case return directly to avoid
// interactions with the bridge.
if (model_error_) {
return;
}
base::Optional<ModelError> error;
bool is_initial_sync = !model_type_state_.initial_sync_done();
model_type_state_ = type_state;
if (is_initial_sync) {
DCHECK(!entity_);
if (updates.empty()) {
error = bridge_->MergeSyncData(base::nullopt);
} else {
DCHECK(!updates[0]->entity->is_deleted());
entity_ = ProcessorEntity::CreateNew(
kNigoriStorageKey, kNigoriClientTagHash, updates[0]->entity->id,
updates[0]->entity->creation_time);
entity_->RecordAcceptedUpdate(*updates[0]);
error = bridge_->MergeSyncData(std::move(*updates[0]->entity));
}
if (error) {
// TODO(mamir): ReportError(*error);
NOTIMPLEMENTED();
}
return;
}
if (updates.empty()) {
bridge_->ApplySyncChanges(/*data=*/base::nullopt);
return;
}
DCHECK(entity_);
// We assume the bridge will issue errors in case of deletions. Therefore, we
// are adding the following DCHECK to simplify the code.
DCHECK(!updates[0]->entity->is_deleted());
if (entity_->UpdateIsReflection(updates[0]->response_version)) {
// Seen this update before; just ignore it.
bridge_->ApplySyncChanges(/*data=*/base::nullopt);
return;
}
if (entity_->IsUnsynced()) {
// TODO(mamir): conflict resolution
NOTIMPLEMENTED();
} else if (!entity_->MatchesData(*updates[0]->entity)) {
// Inform the bridge of the new or updated data.
entity_->RecordAcceptedUpdate(*updates[0]);
error = bridge_->ApplySyncChanges(std::move(*updates[0]->entity));
}
if (error) {
// TODO(mamir): ReportError(*error);
NOTIMPLEMENTED();
return;
}
// There may be new reasons to commit by the time this function is done.
NudgeForCommitIfNeeded();
}
void NigoriModelTypeProcessor::OnSyncStarting(
const DataTypeActivationRequest& request,
StartCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << "Sync is starting for Encryption Keys";
DCHECK(request.error_handler) << "Encryption Keys";
DCHECK(callback) << "Encryption Keys";
DCHECK(!start_callback_) << "Encryption Keys";
DCHECK(!IsConnected()) << "Encryption Keys";
start_callback_ = std::move(callback);
activation_request_ = request;
ConnectIfReady();
}
void NigoriModelTypeProcessor::OnSyncStopping(
SyncStopMetadataFate metadata_fate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Disabling sync for a type shouldn't happen before the model is loaded
// because OnSyncStopping() is not allowed to be called before
// OnSyncStarting() has completed.
DCHECK(!start_callback_);
worker_.reset();
switch (metadata_fate) {
case syncer::KEEP_METADATA: {
break;
}
case syncer::CLEAR_METADATA: {
// The bridge is responsible for deleting all data and metadata upon
// disabling sync.
bridge_->ApplyDisableSyncChanges();
model_ready_to_sync_ = false;
entity_.reset();
model_type_state_ = sync_pb::ModelTypeState();
model_type_state_.mutable_progress_marker()->set_data_type_id(
sync_pb::EntitySpecifics::kNigoriFieldNumber);
// The model is still ready to sync (with the same |bridge_|) and same
// sync metadata.
ModelReadyToSync(bridge_, NigoriMetadataBatch());
break;
}
}
// Do not let any delayed callbacks to be called.
weak_ptr_factory_for_worker_.InvalidateWeakPtrs();
}
void NigoriModelTypeProcessor::GetAllNodesForDebugging(
AllNodesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
}
void NigoriModelTypeProcessor::GetStatusCountersForDebugging(
StatusCountersCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
}
void NigoriModelTypeProcessor::RecordMemoryUsageAndCountsHistograms() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
}
void NigoriModelTypeProcessor::ModelReadyToSync(
NigoriSyncBridge* bridge,
NigoriMetadataBatch nigori_metadata) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(bridge);
DCHECK(!model_ready_to_sync_);
bridge_ = bridge;
model_ready_to_sync_ = true;
// Abort if the model already experienced an error.
if (model_error_) {
return;
}
if (nigori_metadata.model_type_state.initial_sync_done() &&
nigori_metadata.entity_metadata) {
model_type_state_ = std::move(nigori_metadata.model_type_state);
sync_pb::EntityMetadata metadata =
std::move(*nigori_metadata.entity_metadata);
metadata.set_client_tag_hash(kNigoriClientTagHash);
entity_ = ProcessorEntity::CreateFromMetadata(kNigoriStorageKey,
std::move(metadata));
} else {
// First time syncing or persisted data are corrupted; initialize metadata.
model_type_state_.mutable_progress_marker()->set_data_type_id(
sync_pb::EntitySpecifics::kNigoriFieldNumber);
}
ConnectIfReady();
}
void NigoriModelTypeProcessor::Put(std::unique_ptr<EntityData> entity_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(entity_data);
DCHECK(!entity_data->is_deleted());
DCHECK(!entity_data->is_folder);
DCHECK(!entity_data->non_unique_name.empty());
DCHECK(!entity_data->specifics.has_encrypted());
DCHECK_EQ(NIGORI, GetModelTypeFromSpecifics(entity_data->specifics));
DCHECK(entity_);
if (!model_type_state_.initial_sync_done()) {
// Ignore changes before the initial sync is done.
return;
}
if (entity_->MatchesData(*entity_data)) {
// Ignore changes that don't actually change anything.
return;
}
entity_->MakeLocalChange(std::move(entity_data));
NudgeForCommitIfNeeded();
}
NigoriMetadataBatch NigoriModelTypeProcessor::GetMetadata() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsTrackingMetadata());
DCHECK(entity_);
NigoriMetadataBatch nigori_metadata_batch;
nigori_metadata_batch.model_type_state = model_type_state_;
nigori_metadata_batch.entity_metadata = entity_->metadata();
return nigori_metadata_batch;
}
bool NigoriModelTypeProcessor::IsConnectedForTest() const {
return IsConnected();
}
bool NigoriModelTypeProcessor::IsTrackingMetadata() {
return model_type_state_.initial_sync_done();
}
bool NigoriModelTypeProcessor::IsConnected() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return worker_ != nullptr;
}
void NigoriModelTypeProcessor::ConnectIfReady() {
if (!start_callback_) {
return;
}
if (model_error_) {
activation_request_.error_handler.Run(model_error_.value());
start_callback_.Reset();
return;
}
if (!model_ready_to_sync_) {
return;
}
if (!model_type_state_.has_cache_guid()) {
model_type_state_.set_cache_guid(activation_request_.cache_guid);
} else if (model_type_state_.cache_guid() != activation_request_.cache_guid) {
// TODO(mamir): implement error handling in case of cache GUID mismatch.
NOTIMPLEMENTED();
}
// Cache GUID verification earlier above guarantees the user is the same.
model_type_state_.set_authenticated_account_id(
activation_request_.authenticated_account_id);
auto activation_response = std::make_unique<DataTypeActivationResponse>();
activation_response->model_type_state = model_type_state_;
activation_response->type_processor =
std::make_unique<ModelTypeProcessorProxy>(
weak_ptr_factory_for_worker_.GetWeakPtr(),
base::SequencedTaskRunnerHandle::Get());
std::move(start_callback_).Run(std::move(activation_response));
}
void NigoriModelTypeProcessor::NudgeForCommitIfNeeded() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Don't bother sending anything if there's no one to send to.
if (!IsConnected()) {
return;
}
// Don't send anything if the type is not ready to handle commits.
if (!model_type_state_.initial_sync_done()) {
return;
}
// Nudge worker if there are any entities with local changes.
if (entity_->RequiresCommitRequest()) {
worker_->NudgeForCommit();
}
}
} // namespace syncer