blob: 60778bde7e9c95c4506bf4136e30de0da7a6e93e [file]
// Copyright 2016 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/reading_list/core/reading_list_sync_bridge.h"
#include <set>
#include <utility>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/clock.h"
#include "base/trace_event/trace_event.h"
#include "components/reading_list/core/reading_list_model_impl.h"
#include "components/sync/base/data_type_histogram.h"
#include "components/sync/base/deletion_origin.h"
#include "components/sync/model/data_type_local_change_processor.h"
#include "components/sync/model/data_type_store.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/in_memory_metadata_change_list.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/data_type_state.pb.h"
#include "components/sync/protocol/data_type_state_helper.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
ReadingListSyncBridge::ReadingListSyncBridge(
syncer::StorageType storage_type,
syncer::WipeModelUponSyncDisabledBehavior
wipe_model_upon_sync_disabled_behavior,
base::Clock* clock,
std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor)
: DataTypeSyncBridge(std::move(change_processor)),
storage_type_for_uma_(storage_type),
clock_(clock),
wipe_model_upon_sync_disabled_behavior_(
wipe_model_upon_sync_disabled_behavior) {}
ReadingListSyncBridge::~ReadingListSyncBridge() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void ReadingListSyncBridge::ModelReadyToSync(
ReadingListModelImpl* model,
std::unique_ptr<syncer::MetadataBatch> sync_metadata_batch) {
TRACE_EVENT0("ui", "ReadingListSyncBridge::ModelReadyToSync");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(model);
DCHECK(!model_);
model_ = model;
change_processor()->ModelReadyToSync(std::move(sync_metadata_batch));
}
void ReadingListSyncBridge::ReportError(const syncer::ModelError& error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
change_processor()->ReportError(error);
}
void ReadingListSyncBridge::DidAddOrUpdateEntry(
const ReadingListEntry& entry,
syncer::MetadataChangeList* metadata_change_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!change_processor()->IsTrackingMetadata()) {
return;
}
std::unique_ptr<sync_pb::ReadingListSpecifics> pb_entry_sync =
entry.AsReadingListSpecifics();
auto entity_data = std::make_unique<syncer::EntityData>();
*entity_data->specifics.mutable_reading_list() = *pb_entry_sync;
entity_data->name = pb_entry_sync->entry_id();
change_processor()->Put(entry.URL().spec(), std::move(entity_data),
metadata_change_list);
}
void ReadingListSyncBridge::DidRemoveEntry(
const ReadingListEntry& entry,
const base::Location& location,
syncer::MetadataChangeList* metadata_change_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!change_processor()->IsTrackingMetadata()) {
return;
}
change_processor()->Delete(entry.URL().spec(),
syncer::DeletionOrigin::FromLocation(location),
metadata_change_list);
}
// IsTrackingMetadata() continues to be true while ApplyDisableSyncChanges() is
// running, but transitions to false immediately afterwards.
// ongoing_apply_disable_sync_changes_ is used to cause IsTrackingMetadata()
// return false slightly earlier, and before related observer notifications are
// triggered.
bool ReadingListSyncBridge::IsTrackingMetadata() const {
return !ongoing_apply_disable_sync_changes_ &&
change_processor()->IsTrackingMetadata();
}
syncer::StorageType ReadingListSyncBridge::GetStorageTypeForUma() const {
return storage_type_for_uma_;
}
std::unique_ptr<syncer::MetadataChangeList>
ReadingListSyncBridge::CreateMetadataChangeList() {
return std::make_unique<syncer::InMemoryMetadataChangeList>();
}
// Perform the initial merge between local and sync data. This should only be
// called when a data type is first enabled to start syncing, and there is no
// sync metadata. Best effort should be made to match local and sync data. The
// storage keys in the |entity_data| are populated with GetStorageKey(...),
// local and sync copies of the same entity should resolve to the same storage
// key. Any local pieces of data that are not present in sync should immediately
// be Put(...) to the processor before returning. The same MetadataChangeList
// that was passed into this function can be passed to Put(...) calls.
// Delete(...) can also be called but should not be needed for most data types.
// Durable storage writes, if not able to combine all change atomically, should
// save the metadata after the data changes, so that this merge will be re-
// driven by sync if is not completely saved during the current run.
std::optional<syncer::ModelError> ReadingListSyncBridge::MergeFullSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(model_);
// Keep track of the last update of each item.
absl::flat_hash_set<std::string> synced_entries;
std::unique_ptr<ReadingListModelImpl::ScopedReadingListBatchUpdateImpl>
model_batch_updates = model_->BeginBatchUpdatesWithSyncMetadata();
// Merge sync to local data.
for (const auto& change : entity_changes) {
synced_entries.insert(change->storage_key());
const sync_pb::ReadingListSpecifics& specifics =
change->data().specifics.reading_list();
// The specifics validity is guaranteed by IsEntityDataValid().
CHECK(ReadingListEntry::IsSpecificsValid(specifics));
// Deserialize entry.
scoped_refptr<ReadingListEntry> entry(
ReadingListEntry::FromReadingListValidSpecifics(specifics,
clock_->Now()));
scoped_refptr<const ReadingListEntry> existing_entry =
model_->GetEntryByURL(entry->URL());
if (!existing_entry) {
model_->AddEntry(std::move(entry), reading_list::ADDED_VIA_SYNC);
} else {
ReadingListEntry* merged_entry = model_->SyncMergeEntry(std::move(entry));
// Send to sync
std::unique_ptr<sync_pb::ReadingListSpecifics> entry_sync_pb =
merged_entry->AsReadingListSpecifics();
#if !defined(NDEBUG)
scoped_refptr<ReadingListEntry> initial_entry(
ReadingListEntry::FromReadingListValidSpecifics(specifics,
clock_->Now()));
DCHECK(CompareEntriesForSync(*(initial_entry->AsReadingListSpecifics()),
*entry_sync_pb));
#endif
auto entity_data = std::make_unique<syncer::EntityData>();
*(entity_data->specifics.mutable_reading_list()) = *entry_sync_pb;
entity_data->name = entry_sync_pb->entry_id();
change_processor()->Put(entry_sync_pb->entry_id(), std::move(entity_data),
metadata_change_list.get());
}
}
// Commit local only entries to server.
for (const auto& url : model_->GetKeys()) {
scoped_refptr<const ReadingListEntry> entry = model_->GetEntryByURL(url);
if (synced_entries.contains(url.spec())) {
// Entry already exists and has been merged above.
continue;
}
// Local entry has later timestamp. It should be committed to server.
std::unique_ptr<sync_pb::ReadingListSpecifics> entry_pb =
entry->AsReadingListSpecifics();
auto entity_data = std::make_unique<syncer::EntityData>();
*(entity_data->specifics.mutable_reading_list()) = *entry_pb;
entity_data->name = entry_pb->entry_id();
change_processor()->Put(entry_pb->entry_id(), std::move(entity_data),
metadata_change_list.get());
}
metadata_change_list->TransferChangesTo(
model_batch_updates->GetSyncMetadataChangeList());
return {};
}
// Apply changes from the sync server locally.
// Please note that |entity_changes| might have fewer entries than
// |metadata_change_list| in case when some of the data changes are filtered
// out, or even be empty in case when a commit confirmation is processed and
// only the metadata needs to persisted.
std::optional<syncer::ModelError>
ReadingListSyncBridge::ApplyIncrementalSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(model_);
std::unique_ptr<ReadingListModelImpl::ScopedReadingListBatchUpdateImpl>
model_batch_updates = model_->BeginBatchUpdatesWithSyncMetadata();
for (const std::unique_ptr<syncer::EntityChange>& change : entity_changes) {
if (change->type() == syncer::EntityChange::ACTION_DELETE) {
model_->SyncRemoveEntry(GURL(change->storage_key()));
} else {
// Deserialize entry.
const sync_pb::ReadingListSpecifics& specifics =
change->data().specifics.reading_list();
// The specifics validity is guaranteed by IsEntityDataValid().
CHECK(ReadingListEntry::IsSpecificsValid(specifics));
scoped_refptr<ReadingListEntry> entry(
ReadingListEntry::FromReadingListValidSpecifics(specifics,
clock_->Now()));
scoped_refptr<const ReadingListEntry> existing_entry =
model_->GetEntryByURL(entry->URL());
if (!existing_entry) {
model_->AddEntry(std::move(entry), reading_list::ADDED_VIA_SYNC);
} else {
// Merge the local data and the sync data and store the result.
model_->SyncMergeEntry(std::move(entry));
// Note: Do NOT send the merged data back to Sync. Doing that could
// cause ping-pong between two devices that disagree on the "correct"
// form of the data, see e.g. crbug.com/1243254.
// Instead, any local changes will get committed the next time this
// entity is changed.
}
}
}
metadata_change_list->TransferChangesTo(
model_batch_updates->GetSyncMetadataChangeList());
return {};
}
std::unique_ptr<syncer::DataBatch> ReadingListSyncBridge::GetDataForCommit(
StorageKeyList storage_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const std::string& url_string : storage_keys) {
scoped_refptr<const ReadingListEntry> entry =
model_->GetEntryByURL(GURL(url_string));
if (entry) {
AddEntryToBatch(batch.get(), *entry);
}
}
return batch;
}
std::unique_ptr<syncer::DataBatch>
ReadingListSyncBridge::GetAllDataForDebugging() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const auto& url : model_->GetKeys()) {
scoped_refptr<const ReadingListEntry> entry =
model_->GetEntryByURL(GURL(url));
AddEntryToBatch(batch.get(), *entry);
}
return batch;
}
void ReadingListSyncBridge::AddEntryToBatch(syncer::MutableDataBatch* batch,
const ReadingListEntry& entry) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::unique_ptr<sync_pb::ReadingListSpecifics> entry_pb =
entry.AsReadingListSpecifics();
auto entity_data = std::make_unique<syncer::EntityData>();
*(entity_data->specifics.mutable_reading_list()) = *entry_pb;
entity_data->name = entry_pb->entry_id();
batch->Put(entry_pb->entry_id(), std::move(entity_data));
}
std::string ReadingListSyncBridge::GetClientTag(
const syncer::EntityData& entity_data) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return GetStorageKey(entity_data);
}
std::string ReadingListSyncBridge::GetStorageKey(
const syncer::EntityData& entity_data) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return entity_data.specifics.reading_list().entry_id();
}
void ReadingListSyncBridge::ApplyDisableSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
base::AutoReset<bool> auto_reset_is_sync_stopping(
&ongoing_apply_disable_sync_changes_, true);
switch (wipe_model_upon_sync_disabled_behavior_) {
case syncer::WipeModelUponSyncDisabledBehavior::kNever:
CHECK_EQ(storage_type_for_uma_, syncer::StorageType::kUnspecified);
// Fall back to the default behavior (delete metadata only).
DataTypeSyncBridge::ApplyDisableSyncChanges(
std::move(delete_metadata_change_list));
break;
case syncer::WipeModelUponSyncDisabledBehavior::kAlways:
CHECK_EQ(storage_type_for_uma_, syncer::StorageType::kAccount);
// For account storage, in addition to sync metadata deletion (which
// |delete_metadata_change_list| represents), the actual reading list
// entries need to be deleted. This function does both and is even
// robust against orphan or unexpected data in storage.
model_->SyncDeleteAllEntriesAndSyncMetadata();
break;
}
}
bool ReadingListSyncBridge::IsEntityDataValid(
const syncer::EntityData& entity_data) const {
CHECK(entity_data.specifics.ByteSizeLong() != 0);
return ReadingListEntry::IsSpecificsValid(
entity_data.specifics.reading_list());
}
bool ReadingListSyncBridge::CompareEntriesForSync(
const sync_pb::ReadingListSpecifics& lhs,
const sync_pb::ReadingListSpecifics& rhs) {
DCHECK(lhs.entry_id() == rhs.entry_id());
DCHECK(lhs.has_update_time_us());
DCHECK(rhs.has_update_time_us());
DCHECK(lhs.has_update_title_time_us());
DCHECK(rhs.has_update_title_time_us());
DCHECK(lhs.has_creation_time_us());
DCHECK(rhs.has_creation_time_us());
DCHECK(lhs.has_url());
DCHECK(rhs.has_url());
DCHECK(lhs.has_title());
DCHECK(rhs.has_title());
DCHECK(lhs.has_status());
DCHECK(rhs.has_status());
if (rhs.url() != lhs.url() ||
rhs.update_title_time_us() < lhs.update_title_time_us() ||
rhs.creation_time_us() < lhs.creation_time_us() ||
rhs.update_time_us() < lhs.update_time_us()) {
return false;
}
if (rhs.update_time_us() == lhs.update_time_us()) {
if ((rhs.status() == sync_pb::ReadingListSpecifics::UNSEEN &&
lhs.status() != sync_pb::ReadingListSpecifics::UNSEEN) ||
(rhs.status() == sync_pb::ReadingListSpecifics::UNREAD &&
lhs.status() == sync_pb::ReadingListSpecifics::READ))
return false;
}
if (rhs.update_title_time_us() == lhs.update_title_time_us()) {
if (rhs.title().compare(lhs.title()) < 0)
return false;
}
if (rhs.creation_time_us() == lhs.creation_time_us()) {
if (rhs.first_read_time_us() == 0 && lhs.first_read_time_us() != 0) {
return false;
}
if (rhs.first_read_time_us() > lhs.first_read_time_us() &&
lhs.first_read_time_us() != 0) {
return false;
}
}
return true;
}