blob: 57fe59bfb538b33e68d2b628368f238ddc9dbb5d [file] [log] [blame]
// Copyright 2015 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/device_info/device_info_sync_bridge.h"
#include <stdint.h>
#include <algorithm>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "components/sync/base/time.h"
#include "components/sync/device_info/device_info_util.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "components/sync/protocol/sync.pb.h"
namespace syncer {
using base::Time;
using base::TimeDelta;
using sync_pb::DeviceInfoSpecifics;
using sync_pb::EntitySpecifics;
using sync_pb::ModelTypeState;
using Record = ModelTypeStore::Record;
using RecordList = ModelTypeStore::RecordList;
using WriteBatch = ModelTypeStore::WriteBatch;
namespace {
// Find the timestamp for the last time this |device_info| was edited.
Time GetLastUpdateTime(const DeviceInfoSpecifics& specifics) {
if (specifics.has_last_updated_timestamp()) {
return ProtoTimeToTime(specifics.last_updated_timestamp());
} else {
return Time();
}
}
// Converts DeviceInfoSpecifics into a freshly allocated DeviceInfo.
std::unique_ptr<DeviceInfo> SpecificsToModel(
const DeviceInfoSpecifics& specifics) {
return std::make_unique<DeviceInfo>(
specifics.cache_guid(), specifics.client_name(),
specifics.chrome_version(), specifics.sync_user_agent(),
specifics.device_type(), specifics.signin_scoped_device_id());
}
// Allocate a EntityData and copies |specifics| into it.
std::unique_ptr<EntityData> CopyToEntityData(
const DeviceInfoSpecifics& specifics) {
auto entity_data = std::make_unique<EntityData>();
*entity_data->specifics.mutable_device_info() = specifics;
entity_data->non_unique_name = specifics.client_name();
return entity_data;
}
// Converts DeviceInfo into a freshly allocated DeviceInfoSpecifics. Takes
// |last_updated_timestamp| to set because the model object does not contain
// this concept.
std::unique_ptr<DeviceInfoSpecifics> ModelToSpecifics(
const DeviceInfo& info,
int64_t last_updated_timestamp) {
auto specifics = std::make_unique<DeviceInfoSpecifics>();
specifics->set_cache_guid(info.guid());
specifics->set_client_name(info.client_name());
specifics->set_chrome_version(info.chrome_version());
specifics->set_sync_user_agent(info.sync_user_agent());
specifics->set_device_type(info.device_type());
specifics->set_signin_scoped_device_id(info.signin_scoped_device_id());
specifics->set_last_updated_timestamp(last_updated_timestamp);
return specifics;
}
} // namespace
DeviceInfoSyncBridge::DeviceInfoSyncBridge(
LocalDeviceInfoProvider* local_device_info_provider,
OnceModelTypeStoreFactory store_factory,
std::unique_ptr<ModelTypeChangeProcessor> change_processor)
: ModelTypeSyncBridge(std::move(change_processor)),
local_device_info_provider_(local_device_info_provider),
weak_ptr_factory_(this) {
DCHECK(local_device_info_provider);
// This is not threadsafe, but presuably the provider initializes on the same
// thread as us so we're okay.
if (local_device_info_provider->GetLocalDeviceInfo()) {
OnProviderInitialized();
} else {
subscription_ = local_device_info_provider->RegisterOnInitializedCallback(
base::BindRepeating(&DeviceInfoSyncBridge::OnProviderInitialized,
base::Unretained(this)));
}
std::move(store_factory)
.Run(DEVICE_INFO, base::BindOnce(&DeviceInfoSyncBridge::OnStoreCreated,
weak_ptr_factory_.GetWeakPtr()));
}
DeviceInfoSyncBridge::~DeviceInfoSyncBridge() {}
std::unique_ptr<MetadataChangeList>
DeviceInfoSyncBridge::CreateMetadataChangeList() {
return WriteBatch::CreateMetadataChangeList();
}
base::Optional<ModelError> DeviceInfoSyncBridge::MergeSyncData(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_data) {
DCHECK(has_provider_initialized_);
DCHECK(change_processor()->IsTrackingMetadata());
const DeviceInfo* local_info =
local_device_info_provider_->GetLocalDeviceInfo();
// DEVICE_INFO sync is running; DeviceInfo thus exists (it gets cleared only
// synchronously with disabling DEVICE_INFO sync).
DCHECK(local_info);
// Local data should typically be near empty, with the only possible value
// corresponding to this device. This is because on signout all device info
// data is blown away. However, this simplification is being ignored here and
// a full difference is going to be calculated to explore what other bridge
// implementations may look like.
std::set<std::string> local_guids_to_put;
for (const auto& kv : all_data_) {
local_guids_to_put.insert(kv.first);
}
bool has_changes = false;
std::string local_guid = local_info->guid();
std::unique_ptr<WriteBatch> batch = store_->CreateWriteBatch();
for (const auto& change : entity_data) {
const DeviceInfoSpecifics& specifics =
change.data().specifics.device_info();
DCHECK_EQ(change.storage_key(), specifics.cache_guid());
if (specifics.cache_guid() == local_guid) {
// Don't Put local data if it's the same as the remote copy.
if (local_info->Equals(*SpecificsToModel(specifics))) {
local_guids_to_put.erase(local_guid);
}
} else {
// Remote data wins conflicts.
local_guids_to_put.erase(specifics.cache_guid());
has_changes = true;
StoreSpecifics(std::make_unique<DeviceInfoSpecifics>(specifics),
batch.get());
}
}
for (const std::string& guid : local_guids_to_put) {
change_processor()->Put(guid, CopyToEntityData(*all_data_[guid]),
metadata_change_list.get());
}
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
CommitAndNotify(std::move(batch), has_changes);
return {};
}
base::Optional<ModelError> DeviceInfoSyncBridge::ApplySyncChanges(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_changes) {
DCHECK(has_provider_initialized_);
const DeviceInfo* local_info =
local_device_info_provider_->GetLocalDeviceInfo();
// DEVICE_INFO sync is running; DeviceInfo thus exists (it gets cleared only
// synchronously with disabling DEVICE_INFO sync).
DCHECK(local_info);
std::unique_ptr<WriteBatch> batch = store_->CreateWriteBatch();
bool has_changes = false;
for (EntityChange& change : entity_changes) {
const std::string guid = change.storage_key();
// Each device is the authoritative source for itself, ignore any remote
// changes that have our local cache guid.
if (guid == local_info->guid()) {
continue;
}
if (change.type() == EntityChange::ACTION_DELETE) {
has_changes |= DeleteSpecifics(guid, batch.get());
} else {
const DeviceInfoSpecifics& specifics =
change.data().specifics.device_info();
DCHECK(guid == specifics.cache_guid());
StoreSpecifics(std::make_unique<DeviceInfoSpecifics>(specifics),
batch.get());
has_changes = true;
}
}
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
CommitAndNotify(std::move(batch), has_changes);
return {};
}
void DeviceInfoSyncBridge::GetData(StorageKeyList storage_keys,
DataCallback callback) {
auto batch = std::make_unique<MutableDataBatch>();
for (const auto& key : storage_keys) {
const auto& iter = all_data_.find(key);
if (iter != all_data_.end()) {
DCHECK_EQ(key, iter->second->cache_guid());
batch->Put(key, CopyToEntityData(*iter->second));
}
}
std::move(callback).Run(std::move(batch));
}
void DeviceInfoSyncBridge::GetAllDataForDebugging(DataCallback callback) {
auto batch = std::make_unique<MutableDataBatch>();
for (const auto& kv : all_data_) {
batch->Put(kv.first, CopyToEntityData(*kv.second));
}
std::move(callback).Run(std::move(batch));
}
std::string DeviceInfoSyncBridge::GetClientTag(const EntityData& entity_data) {
DCHECK(entity_data.specifics.has_device_info());
return DeviceInfoUtil::SpecificsToTag(entity_data.specifics.device_info());
}
std::string DeviceInfoSyncBridge::GetStorageKey(const EntityData& entity_data) {
DCHECK(entity_data.specifics.has_device_info());
return entity_data.specifics.device_info().cache_guid();
}
ModelTypeSyncBridge::StopSyncResponse
DeviceInfoSyncBridge::ApplyStopSyncChanges(
std::unique_ptr<MetadataChangeList> delete_metadata_change_list) {
// TODO(skym, crbug.com/659263): Would it be reasonable to pulse_timer_.Stop()
// or subscription_.reset() here?
// Remove all local data, if sync is being disabled, the user has expressed
// their desire to not have knowledge about other devices.
if (delete_metadata_change_list) {
store_->DeleteAllDataAndMetadata(base::DoNothing());
if (!all_data_.empty()) {
all_data_.clear();
NotifyObservers();
}
}
return StopSyncResponse::kModelStillReadyToSync;
}
bool DeviceInfoSyncBridge::IsSyncing() const {
return !all_data_.empty();
}
std::unique_ptr<DeviceInfo> DeviceInfoSyncBridge::GetDeviceInfo(
const std::string& client_id) const {
const ClientIdToSpecifics::const_iterator iter = all_data_.find(client_id);
if (iter == all_data_.end()) {
return std::unique_ptr<DeviceInfo>();
}
return SpecificsToModel(*iter->second);
}
std::vector<std::unique_ptr<DeviceInfo>>
DeviceInfoSyncBridge::GetAllDeviceInfo() const {
std::vector<std::unique_ptr<DeviceInfo>> list;
for (auto iter = all_data_.begin(); iter != all_data_.end(); ++iter) {
list.push_back(SpecificsToModel(*iter->second));
}
return list;
}
void DeviceInfoSyncBridge::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DeviceInfoSyncBridge::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
int DeviceInfoSyncBridge::CountActiveDevices() const {
return CountActiveDevices(Time::Now());
}
// static
std::unique_ptr<ModelTypeStore>
DeviceInfoSyncBridge::DestroyAndStealStoreForTest(
std::unique_ptr<DeviceInfoSyncBridge> bridge) {
return std::move(bridge->store_);
}
bool DeviceInfoSyncBridge::IsPulseTimerRunningForTest() const {
return pulse_timer_.IsRunning();
}
void DeviceInfoSyncBridge::ForcePulseForTest() {
SendLocalData();
}
void DeviceInfoSyncBridge::NotifyObservers() {
for (auto& observer : observers_)
observer.OnDeviceInfoChange();
}
void DeviceInfoSyncBridge::StoreSpecifics(
std::unique_ptr<DeviceInfoSpecifics> specifics,
WriteBatch* batch) {
const std::string guid = specifics->cache_guid();
batch->WriteData(guid, specifics->SerializeAsString());
all_data_[guid] = std::move(specifics);
}
bool DeviceInfoSyncBridge::DeleteSpecifics(const std::string& guid,
WriteBatch* batch) {
ClientIdToSpecifics::const_iterator iter = all_data_.find(guid);
if (iter != all_data_.end()) {
batch->DeleteData(guid);
all_data_.erase(iter);
return true;
} else {
return false;
}
}
void DeviceInfoSyncBridge::OnProviderInitialized() {
// Now that the provider has initialized, remove the subscription. The bridge
// should only need to give the processor metadata upon initialization. If
// sync is disabled and enabled, our provider will try to retrigger this
// event, but we do not want to send any more metadata to the processor.
// TODO(skym, crbug.com/672600): Handle re-initialization and start the pulse
// timer.
subscription_.reset();
has_provider_initialized_ = true;
LoadMetadataIfReady();
}
void DeviceInfoSyncBridge::OnStoreCreated(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<ModelTypeStore> store) {
if (error) {
change_processor()->ReportError(*error);
return;
}
store_ = std::move(store);
store_->ReadAllData(base::BindOnce(&DeviceInfoSyncBridge::OnReadAllData,
weak_ptr_factory_.GetWeakPtr()));
}
void DeviceInfoSyncBridge::OnReadAllData(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<RecordList> record_list) {
if (error) {
change_processor()->ReportError(*error);
return;
}
for (const Record& r : *record_list) {
std::unique_ptr<DeviceInfoSpecifics> specifics =
std::make_unique<DeviceInfoSpecifics>();
if (specifics->ParseFromString(r.value)) {
all_data_[specifics->cache_guid()] = std::move(specifics);
} else {
change_processor()->ReportError(
{FROM_HERE, "Failed to deserialize specifics."});
return;
}
}
has_data_loaded_ = true;
LoadMetadataIfReady();
}
void DeviceInfoSyncBridge::LoadMetadataIfReady() {
if (has_data_loaded_ && has_provider_initialized_) {
store_->ReadAllMetadata(
base::BindOnce(&DeviceInfoSyncBridge::OnReadAllMetadata,
weak_ptr_factory_.GetWeakPtr()));
}
}
void DeviceInfoSyncBridge::OnReadAllMetadata(
const base::Optional<ModelError>& error,
std::unique_ptr<MetadataBatch> metadata_batch) {
if (error) {
change_processor()->ReportError(*error);
return;
}
change_processor()->ModelReadyToSync(std::move(metadata_batch));
ReconcileLocalAndStored();
}
void DeviceInfoSyncBridge::OnCommit(
const base::Optional<syncer::ModelError>& error) {
if (error) {
change_processor()->ReportError(*error);
}
}
void DeviceInfoSyncBridge::ReconcileLocalAndStored() {
// On initial syncing we will have a change processor here, but it will not be
// tracking changes. We need to persist a copy of our local device info to
// disk, but the Put call to the processor will be ignored. That should be
// fine however, as the discrepancy will be picked up later in merge. We don't
// bother trying to track this case and act intelligently because simply not
// much of a benefit in doing so.
DCHECK(has_provider_initialized_);
const DeviceInfo* current_info =
local_device_info_provider_->GetLocalDeviceInfo();
// Must ensure |pulse_timer_| is started even if sync is in the process of
// being disabled. TODO(skym, crbug.com/672600): Remove this timer Start(), as
// it should be started when the provider re-initializes instead.
if (current_info == nullptr) {
pulse_timer_.Start(FROM_HERE, DeviceInfoUtil::kPulseInterval,
base::BindRepeating(&DeviceInfoSyncBridge::SendLocalData,
base::Unretained(this)));
return;
}
auto iter = all_data_.find(current_info->guid());
// Convert to DeviceInfo for Equals function.
if (iter != all_data_.end() &&
current_info->Equals(*SpecificsToModel(*iter->second))) {
const TimeDelta pulse_delay(DeviceInfoUtil::CalculatePulseDelay(
GetLastUpdateTime(*iter->second), Time::Now()));
if (!pulse_delay.is_zero()) {
pulse_timer_.Start(
FROM_HERE, pulse_delay,
base::BindRepeating(&DeviceInfoSyncBridge::SendLocalData,
base::Unretained(this)));
return;
}
}
SendLocalData();
}
void DeviceInfoSyncBridge::SendLocalData() {
DCHECK(has_provider_initialized_);
// It is possible that the provider no longer has data for us, such as when
// the user signs out. No-op this pulse, but keep the timer going in case sync
// is enabled later.
if (local_device_info_provider_->GetLocalDeviceInfo() != nullptr) {
std::unique_ptr<DeviceInfoSpecifics> specifics =
ModelToSpecifics(*local_device_info_provider_->GetLocalDeviceInfo(),
TimeToProtoTime(Time::Now()));
std::unique_ptr<WriteBatch> batch = store_->CreateWriteBatch();
if (change_processor()->IsTrackingMetadata()) {
change_processor()->Put(specifics->cache_guid(),
CopyToEntityData(*specifics),
batch->GetMetadataChangeList());
}
StoreSpecifics(std::move(specifics), batch.get());
CommitAndNotify(std::move(batch), true);
}
pulse_timer_.Start(FROM_HERE, DeviceInfoUtil::kPulseInterval,
base::BindRepeating(&DeviceInfoSyncBridge::SendLocalData,
base::Unretained(this)));
}
void DeviceInfoSyncBridge::CommitAndNotify(std::unique_ptr<WriteBatch> batch,
bool should_notify) {
store_->CommitWriteBatch(std::move(batch),
base::BindOnce(&DeviceInfoSyncBridge::OnCommit,
weak_ptr_factory_.GetWeakPtr()));
if (should_notify) {
NotifyObservers();
}
}
int DeviceInfoSyncBridge::CountActiveDevices(const Time now) const {
return std::count_if(all_data_.begin(), all_data_.end(),
[now](ClientIdToSpecifics::const_reference pair) {
return DeviceInfoUtil::IsActive(
GetLastUpdateTime(*pair.second), now);
});
}
} // namespace syncer