| // Copyright 2017 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 "chrome/browser/chromeos/printing/printers_sync_bridge.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/optional.h" |
| #include "base/stl_util.h" |
| #include "base/task_scheduler/post_task.h" |
| #include "components/sync/base/report_unrecoverable_error.h" |
| #include "components/sync/model/model_type_change_processor.h" |
| #include "components/sync/model/model_type_store.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 chromeos { |
| |
| namespace { |
| |
| using Result = syncer::ModelTypeStore::Result; |
| |
| using syncer::ConflictResolution; |
| using syncer::EntityChange; |
| using syncer::EntityChangeList; |
| using syncer::EntityData; |
| using syncer::ModelTypeChangeProcessor; |
| using syncer::ModelTypeStore; |
| using syncer::MetadataChangeList; |
| |
| std::unique_ptr<EntityData> CopyToEntityData( |
| const sync_pb::PrinterSpecifics& specifics) { |
| auto entity_data = std::make_unique<EntityData>(); |
| *entity_data->specifics.mutable_printer() = specifics; |
| entity_data->non_unique_name = |
| specifics.display_name().empty() ? "PRINTER" : specifics.display_name(); |
| return entity_data; |
| } |
| |
| } // namespace |
| |
| // Delegate class which helps to manage the ModelTypeStore. |
| class PrintersSyncBridge::StoreProxy { |
| public: |
| StoreProxy(PrintersSyncBridge* owner, |
| syncer::OnceModelTypeStoreFactory callback) |
| : owner_(owner), weak_ptr_factory_(this) { |
| std::move(callback).Run(syncer::PRINTERS, |
| base::BindOnce(&StoreProxy::OnStoreCreated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Returns true if the store has been initialized. |
| bool Ready() { return store_.get() != nullptr; } |
| |
| // Returns a new WriteBatch. |
| std::unique_ptr<ModelTypeStore::WriteBatch> CreateWriteBatch() { |
| DCHECK(store_); |
| return store_->CreateWriteBatch(); |
| } |
| |
| // Commits writes to the database and updates metadata. |
| void Commit(std::unique_ptr<ModelTypeStore::WriteBatch> batch) { |
| DCHECK(store_); |
| store_->CommitWriteBatch( |
| std::move(batch), |
| base::Bind(&StoreProxy::OnCommit, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| private: |
| // Callback for ModelTypeStore initialization. |
| void OnStoreCreated(Result result, std::unique_ptr<ModelTypeStore> store) { |
| if (result == Result::SUCCESS) { |
| store_ = std::move(store); |
| store_->ReadAllData(base::Bind(&StoreProxy::OnReadAllData, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| owner_->change_processor()->ReportError( |
| {FROM_HERE, "ModelTypeStore creation failed."}); |
| } |
| } |
| |
| void OnReadAllData(Result result, |
| std::unique_ptr<ModelTypeStore::RecordList> record_list) { |
| if (result != Result::SUCCESS) { |
| owner_->change_processor()->ReportError( |
| {FROM_HERE, "Initial load of data failed"}); |
| return; |
| } |
| |
| bool error = false; |
| { |
| base::AutoLock lock(owner_->data_lock_); |
| for (const ModelTypeStore::Record& r : *record_list) { |
| auto specifics = std::make_unique<sync_pb::PrinterSpecifics>(); |
| if (specifics->ParseFromString(r.value)) { |
| auto& dest = owner_->all_data_[specifics->id()]; |
| dest = std::move(specifics); |
| } else { |
| error = true; |
| } |
| } |
| } |
| owner_->NotifyPrintersUpdated(); |
| |
| if (error) { |
| owner_->change_processor()->ReportError( |
| {FROM_HERE, "Failed to deserialize all specifics."}); |
| return; |
| } |
| |
| // Data loaded. Load metadata. |
| store_->ReadAllMetadata(base::Bind(&StoreProxy::OnReadAllMetadata, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Callback to handle commit errors. |
| void OnCommit(ModelTypeStore::Result result) { |
| if (result != Result::SUCCESS) { |
| LOG(WARNING) << "Failed to commit operation to store"; |
| owner_->change_processor()->ReportError( |
| {FROM_HERE, "Failed to commit to store"}); |
| } |
| } |
| |
| void OnReadAllMetadata( |
| base::Optional<syncer::ModelError> error, |
| std::unique_ptr<syncer::MetadataBatch> metadata_batch) { |
| if (error) { |
| owner_->change_processor()->ReportError(*error); |
| return; |
| } |
| |
| owner_->change_processor()->ModelReadyToSync(std::move(metadata_batch)); |
| } |
| |
| PrintersSyncBridge* owner_; |
| |
| std::unique_ptr<ModelTypeStore> store_; |
| base::WeakPtrFactory<StoreProxy> weak_ptr_factory_; |
| }; |
| |
| PrintersSyncBridge::PrintersSyncBridge( |
| syncer::OnceModelTypeStoreFactory callback, |
| const base::RepeatingClosure& error_callback) |
| : ModelTypeSyncBridge(base::BindRepeating(&ModelTypeChangeProcessor::Create, |
| error_callback), |
| syncer::PRINTERS), |
| store_delegate_(std::make_unique<StoreProxy>(this, std::move(callback))), |
| observers_(new base::ObserverListThreadSafe<Observer>()) {} |
| |
| PrintersSyncBridge::~PrintersSyncBridge() {} |
| |
| std::unique_ptr<MetadataChangeList> |
| PrintersSyncBridge::CreateMetadataChangeList() { |
| return ModelTypeStore::WriteBatch::CreateMetadataChangeList(); |
| } |
| |
| base::Optional<syncer::ModelError> PrintersSyncBridge::MergeSyncData( |
| std::unique_ptr<MetadataChangeList> metadata_change_list, |
| syncer::EntityChangeList entity_data) { |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_delegate_->CreateWriteBatch(); |
| std::set<std::string> sync_entity_ids; |
| { |
| base::AutoLock lock(data_lock_); |
| // Store the new data locally. |
| for (const auto& change : entity_data) { |
| const sync_pb::PrinterSpecifics& specifics = |
| change.data().specifics.printer(); |
| |
| DCHECK_EQ(change.storage_key(), specifics.id()); |
| sync_entity_ids.insert(specifics.id()); |
| |
| // Write the update to local storage even if we already have it. |
| StoreSpecifics(std::make_unique<sync_pb::PrinterSpecifics>(specifics), |
| batch.get()); |
| } |
| |
| // Inform the change processor of the new local entities and generate |
| // appropriate metadata. |
| for (const auto& entry : all_data_) { |
| const std::string& local_entity_id = entry.first; |
| if (!base::ContainsKey(sync_entity_ids, local_entity_id)) { |
| // Only local objects which were not updated are uploaded. Objects for |
| // which there was a remote copy are overwritten. |
| change_processor()->Put(local_entity_id, |
| CopyToEntityData(*entry.second), |
| metadata_change_list.get()); |
| } |
| } |
| } |
| |
| NotifyPrintersUpdated(); |
| batch->TransferMetadataChanges(std::move(metadata_change_list)); |
| store_delegate_->Commit(std::move(batch)); |
| return {}; |
| } |
| |
| base::Optional<syncer::ModelError> PrintersSyncBridge::ApplySyncChanges( |
| std::unique_ptr<MetadataChangeList> metadata_change_list, |
| EntityChangeList entity_changes) { |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_delegate_->CreateWriteBatch(); |
| { |
| base::AutoLock lock(data_lock_); |
| // For all the entities from the server, apply changes. |
| for (const EntityChange& change : entity_changes) { |
| // We register the entity's storage key as our printer ids since they're |
| // globally unique. |
| const std::string& id = change.storage_key(); |
| if (change.type() == EntityChange::ACTION_DELETE) { |
| // Server says delete, try to remove locally. |
| DeleteSpecifics(id, batch.get()); |
| } else { |
| // Server says update, overwrite whatever is local. Conflict resolution |
| // guarantees that this will be the newest version of the object. |
| const sync_pb::PrinterSpecifics& specifics = |
| change.data().specifics.printer(); |
| DCHECK_EQ(id, specifics.id()); |
| StoreSpecifics(std::make_unique<sync_pb::PrinterSpecifics>(specifics), |
| batch.get()); |
| } |
| } |
| } |
| |
| NotifyPrintersUpdated(); |
| // Update the local database with metadata for the incoming changes. |
| batch->TransferMetadataChanges(std::move(metadata_change_list)); |
| |
| store_delegate_->Commit(std::move(batch)); |
| return {}; |
| } |
| |
| void PrintersSyncBridge::GetData(StorageKeyList storage_keys, |
| DataCallback callback) { |
| auto batch = std::make_unique<syncer::MutableDataBatch>(); |
| { |
| base::AutoLock lock(data_lock_); |
| for (const auto& key : storage_keys) { |
| auto found = all_data_.find(key); |
| if (found != all_data_.end()) { |
| batch->Put(key, CopyToEntityData(*found->second)); |
| } |
| } |
| } |
| callback.Run(std::move(batch)); |
| } |
| |
| void PrintersSyncBridge::GetAllData(DataCallback callback) { |
| auto batch = std::make_unique<syncer::MutableDataBatch>(); |
| { |
| base::AutoLock lock(data_lock_); |
| for (const auto& entry : all_data_) { |
| batch->Put(entry.first, CopyToEntityData(*entry.second)); |
| } |
| } |
| callback.Run(std::move(batch)); |
| } |
| |
| std::string PrintersSyncBridge::GetClientTag(const EntityData& entity_data) { |
| // Printers were never synced prior to USS so this can match GetStorageKey. |
| return GetStorageKey(entity_data); |
| } |
| |
| std::string PrintersSyncBridge::GetStorageKey(const EntityData& entity_data) { |
| DCHECK(entity_data.specifics.has_printer()); |
| return entity_data.specifics.printer().id(); |
| } |
| |
| // Picks the entity with the most recent updated time as the canonical version. |
| ConflictResolution PrintersSyncBridge::ResolveConflict( |
| const EntityData& local_data, |
| const EntityData& remote_data) const { |
| DCHECK(local_data.specifics.has_printer()); |
| DCHECK(remote_data.specifics.has_printer()); |
| |
| const sync_pb::PrinterSpecifics& local_printer = |
| local_data.specifics.printer(); |
| const sync_pb::PrinterSpecifics& remote_printer = |
| remote_data.specifics.printer(); |
| |
| if (local_printer.updated_timestamp() > remote_printer.updated_timestamp()) { |
| return ConflictResolution::UseLocal(); |
| } |
| |
| return ConflictResolution::UseRemote(); |
| } |
| |
| void PrintersSyncBridge::AddPrinter( |
| std::unique_ptr<sync_pb::PrinterSpecifics> printer) { |
| { |
| base::AutoLock lock(data_lock_); |
| AddPrinterLocked(std::move(printer)); |
| } |
| NotifyPrintersUpdated(); |
| } |
| |
| bool PrintersSyncBridge::UpdatePrinter( |
| std::unique_ptr<sync_pb::PrinterSpecifics> printer) { |
| bool res; |
| { |
| base::AutoLock lock(data_lock_); |
| res = UpdatePrinterLocked(std::move(printer)); |
| } |
| NotifyPrintersUpdated(); |
| return res; |
| } |
| |
| bool PrintersSyncBridge::UpdatePrinterLocked( |
| std::unique_ptr<sync_pb::PrinterSpecifics> printer) { |
| data_lock_.AssertAcquired(); |
| DCHECK(printer->has_id()); |
| auto iter = all_data_.find(printer->id()); |
| if (iter == all_data_.end()) { |
| AddPrinterLocked(std::move(printer)); |
| return true; |
| } |
| |
| // Modify the printer in-place then notify the change processor. |
| sync_pb::PrinterSpecifics* merged = iter->second.get(); |
| merged->MergeFrom(*printer); |
| merged->set_updated_timestamp(base::Time::Now().ToJavaTime()); |
| CommitPrinterPut(*merged); |
| |
| return false; |
| } |
| |
| bool PrintersSyncBridge::RemovePrinter(const std::string& id) { |
| DCHECK(store_delegate_->Ready()); |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_delegate_->CreateWriteBatch(); |
| { |
| base::AutoLock lock(data_lock_); |
| if (!DeleteSpecifics(id, batch.get())) { |
| LOG(WARNING) << "Could not find printer" << id; |
| return false; |
| } |
| } |
| |
| if (change_processor()->IsTrackingMetadata()) { |
| change_processor()->Delete(id, batch->GetMetadataChangeList()); |
| } |
| store_delegate_->Commit(std::move(batch)); |
| |
| return true; |
| } |
| |
| std::vector<sync_pb::PrinterSpecifics> PrintersSyncBridge::GetAllPrinters() |
| const { |
| base::AutoLock lock(data_lock_); |
| std::vector<sync_pb::PrinterSpecifics> printers; |
| for (auto& entry : all_data_) { |
| printers.push_back(*entry.second); |
| } |
| |
| return printers; |
| } |
| |
| base::Optional<sync_pb::PrinterSpecifics> PrintersSyncBridge::GetPrinter( |
| const std::string& id) const { |
| base::AutoLock lock(data_lock_); |
| auto iter = all_data_.find(id); |
| if (iter == all_data_.end()) { |
| return {}; |
| } |
| |
| return {*iter->second}; |
| } |
| |
| void PrintersSyncBridge::CommitPrinterPut( |
| const sync_pb::PrinterSpecifics& printer) { |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_delegate_->CreateWriteBatch(); |
| if (change_processor()->IsTrackingMetadata()) { |
| change_processor()->Put(printer.id(), CopyToEntityData(printer), |
| batch->GetMetadataChangeList()); |
| } |
| batch->WriteData(printer.id(), printer.SerializeAsString()); |
| |
| store_delegate_->Commit(std::move(batch)); |
| } |
| |
| void PrintersSyncBridge::AddPrinterLocked( |
| std::unique_ptr<sync_pb::PrinterSpecifics> printer) { |
| // TODO(skau): Benchmark this code. Make sure it doesn't hold onto the lock |
| // for too long. |
| data_lock_.AssertAcquired(); |
| printer->set_updated_timestamp(base::Time::Now().ToJavaTime()); |
| |
| CommitPrinterPut(*printer); |
| auto& dest = all_data_[printer->id()]; |
| dest = std::move(printer); |
| } |
| |
| void PrintersSyncBridge::StoreSpecifics( |
| std::unique_ptr<sync_pb::PrinterSpecifics> specifics, |
| ModelTypeStore::WriteBatch* batch) { |
| data_lock_.AssertAcquired(); |
| const std::string id = specifics->id(); |
| batch->WriteData(id, specifics->SerializeAsString()); |
| all_data_[id] = std::move(specifics); |
| } |
| |
| bool PrintersSyncBridge::DeleteSpecifics(const std::string& id, |
| ModelTypeStore::WriteBatch* batch) { |
| data_lock_.AssertAcquired(); |
| auto iter = all_data_.find(id); |
| if (iter != all_data_.end()) { |
| batch->DeleteData(id); |
| all_data_.erase(iter); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void PrintersSyncBridge::AddObserver(Observer* obs) { |
| observers_->AddObserver(obs); |
| } |
| |
| void PrintersSyncBridge::RemoveObserver(Observer* obs) { |
| observers_->RemoveObserver(obs); |
| } |
| |
| void PrintersSyncBridge::NotifyPrintersUpdated() { |
| observers_->Notify(FROM_HERE, |
| &PrintersSyncBridge::Observer::OnPrintersUpdated); |
| } |
| |
| } // namespace chromeos |