| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/printing/printers_sync_bridge.h" |
| |
| #include <set> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/trace_event/trace_event.h" |
| #include "chrome/browser/ash/printing/specifics_translation.h" |
| #include "chromeos/printing/printer_configuration.h" |
| #include "components/sync/base/deletion_origin.h" |
| #include "components/sync/base/report_unrecoverable_error.h" |
| #include "components/sync/model/client_tag_based_data_type_processor.h" |
| #include "components/sync/model/data_type_local_change_processor.h" |
| #include "components/sync/model/mutable_data_batch.h" |
| #include "components/sync/protocol/entity_specifics.pb.h" |
| #include "components/sync/protocol/printer_specifics.pb.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| using syncer::ClientTagBasedDataTypeProcessor; |
| using syncer::ConflictResolution; |
| using syncer::DataTypeLocalChangeProcessor; |
| using syncer::DataTypeStore; |
| using syncer::EntityChange; |
| using syncer::EntityChangeList; |
| using syncer::EntityData; |
| 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->name = |
| specifics.display_name().empty() ? "PRINTER" : specifics.display_name(); |
| return entity_data; |
| } |
| |
| // Computes the make_and_model field for old |specifics| where it is missing. |
| // Returns true if an update was made. make_and_model is computed from the |
| // manufacturer and model strings. |
| bool MigrateMakeAndModel(sync_pb::PrinterSpecifics* specifics) { |
| if (specifics->has_make_and_model()) { |
| return false; |
| } |
| |
| specifics->set_make_and_model( |
| MakeAndModel(specifics->manufacturer(), specifics->model())); |
| return true; |
| } |
| |
| // If |specifics|'s PPD reference has both autoconf and another option selected, |
| // we strip the autoconf flag and return true, false otherwise. |
| bool ResolveInvalidPpdReference(sync_pb::PrinterSpecifics* specifics) { |
| auto* ppd_ref = specifics->mutable_ppd_reference(); |
| |
| if (!ppd_ref->autoconf()) |
| return false; |
| |
| if (!ppd_ref->has_user_supplied_ppd_url() && |
| !ppd_ref->has_effective_make_and_model()) { |
| return false; |
| } |
| |
| ppd_ref->clear_autoconf(); |
| return true; |
| } |
| |
| } // namespace |
| |
| // Delegate class which helps to manage the DataTypeStore. |
| class PrintersSyncBridge::StoreProxy { |
| public: |
| StoreProxy(PrintersSyncBridge* owner, |
| syncer::OnceDataTypeStoreFactory callback) |
| : owner_(owner) { |
| 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<DataTypeStore::WriteBatch> CreateWriteBatch() { |
| DCHECK(store_); |
| return store_->CreateWriteBatch(); |
| } |
| |
| // Commits writes to the database and updates metadata. |
| void Commit(std::unique_ptr<DataTypeStore::WriteBatch> batch) { |
| DCHECK(store_); |
| store_->CommitWriteBatch( |
| std::move(batch), |
| base::BindOnce(&StoreProxy::OnCommit, weak_ptr_factory_.GetWeakPtr())); |
| owner_->NotifyPrintersUpdated(); |
| } |
| |
| private: |
| // Callback for DataTypeStore initialization. |
| void OnStoreCreated(const std::optional<syncer::ModelError>& error, |
| std::unique_ptr<DataTypeStore> store) { |
| if (error) { |
| owner_->change_processor()->ReportError(*error); |
| return; |
| } |
| |
| store_ = std::move(store); |
| store_->ReadAllData(base::BindOnce(&StoreProxy::OnReadAllData, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void OnReadAllData(const std::optional<syncer::ModelError>& error, |
| std::unique_ptr<DataTypeStore::RecordList> record_list) { |
| if (error) { |
| owner_->change_processor()->ReportError(*error); |
| return; |
| } |
| |
| bool parse_error = false; |
| { |
| base::AutoLock lock(owner_->data_lock_); |
| for (const DataTypeStore::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 { |
| parse_error = true; |
| } |
| } |
| } |
| owner_->NotifyPrintersUpdated(); |
| |
| if (parse_error) { |
| owner_->change_processor()->ReportError( |
| {FROM_HERE, |
| syncer::ModelError::Type::kPrintersFailedToDeserializeSpecifics}); |
| return; |
| } |
| |
| // Data loaded. Load metadata. |
| store_->ReadAllMetadata(base::BindOnce(&StoreProxy::OnReadAllMetadata, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Callback to handle commit errors. |
| void OnCommit(const std::optional<syncer::ModelError>& error) { |
| if (error) { |
| LOG(WARNING) << "Failed to commit operation to store"; |
| owner_->change_processor()->ReportError(*error); |
| return; |
| } |
| } |
| |
| void OnReadAllMetadata( |
| const std::optional<syncer::ModelError>& error, |
| std::unique_ptr<syncer::MetadataBatch> metadata_batch) { |
| TRACE_EVENT0( |
| "ui", |
| "ash::{anonympus}::PrintersSyncBridge::StoreProxy::OnReadAllMetadata"); |
| if (error) { |
| owner_->change_processor()->ReportError(*error); |
| return; |
| } |
| |
| owner_->change_processor()->ModelReadyToSync(std::move(metadata_batch)); |
| } |
| |
| raw_ptr<PrintersSyncBridge> owner_; |
| |
| std::unique_ptr<DataTypeStore> store_; |
| base::WeakPtrFactory<StoreProxy> weak_ptr_factory_{this}; |
| }; |
| |
| PrintersSyncBridge::PrintersSyncBridge( |
| syncer::OnceDataTypeStoreFactory callback, |
| base::RepeatingClosure error_callback) |
| : DataTypeSyncBridge(std::make_unique<ClientTagBasedDataTypeProcessor>( |
| syncer::PRINTERS, |
| std::move(error_callback))), |
| store_delegate_(std::make_unique<StoreProxy>(this, std::move(callback))), |
| observers_(new base::ObserverListThreadSafe<Observer>()) {} |
| |
| PrintersSyncBridge::~PrintersSyncBridge() = default; |
| |
| std::unique_ptr<MetadataChangeList> |
| PrintersSyncBridge::CreateMetadataChangeList() { |
| return DataTypeStore::WriteBatch::CreateMetadataChangeList(); |
| } |
| |
| std::optional<syncer::ModelError> PrintersSyncBridge::MergeFullSyncData( |
| std::unique_ptr<MetadataChangeList> metadata_change_list, |
| syncer::EntityChangeList entity_data) { |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| |
| std::unique_ptr<DataTypeStore::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; |
| |
| // Migrate old schema to new combined one (crbug.com/737809). |
| bool migrated = MigrateMakeAndModel(entry.second.get()); |
| |
| // Clean up invalid ppd references (crbug.com/987869). |
| bool resolved = ResolveInvalidPpdReference(entry.second.get()); |
| |
| if (migrated || resolved || |
| !base::Contains(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->TakeMetadataChangesFrom(std::move(metadata_change_list)); |
| store_delegate_->Commit(std::move(batch)); |
| return {}; |
| } |
| |
| std::optional<syncer::ModelError> |
| PrintersSyncBridge::ApplyIncrementalSyncChanges( |
| std::unique_ptr<MetadataChangeList> metadata_change_list, |
| EntityChangeList entity_changes) { |
| std::unique_ptr<DataTypeStore::WriteBatch> batch = |
| store_delegate_->CreateWriteBatch(); |
| { |
| base::AutoLock lock(data_lock_); |
| // For all the entities from the server, apply changes. |
| for (const std::unique_ptr<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->TakeMetadataChangesFrom(std::move(metadata_change_list)); |
| |
| store_delegate_->Commit(std::move(batch)); |
| return {}; |
| } |
| |
| std::unique_ptr<syncer::DataBatch> PrintersSyncBridge::GetDataForCommit( |
| StorageKeyList storage_keys) { |
| 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)); |
| } |
| } |
| } |
| return batch; |
| } |
| |
| std::unique_ptr<syncer::DataBatch> |
| PrintersSyncBridge::GetAllDataForDebugging() { |
| 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)); |
| } |
| } |
| return batch; |
| } |
| |
| std::string PrintersSyncBridge::GetClientTag( |
| const EntityData& entity_data) const { |
| // Printers were never synced prior to USS so this can match GetStorageKey. |
| return GetStorageKey(entity_data); |
| } |
| |
| std::string PrintersSyncBridge::GetStorageKey( |
| const EntityData& entity_data) const { |
| 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 std::string& storage_key, |
| const EntityData& remote_data) const { |
| DCHECK(remote_data.specifics.has_printer()); |
| |
| auto iter = all_data_.find(storage_key); |
| // If the local printer doesn't exist, it must have been deleted. In this |
| // case, use the remote one. |
| if (iter == all_data_.end()) { |
| return ConflictResolution::kUseRemote; |
| } |
| const sync_pb::PrinterSpecifics& local_printer = *iter->second; |
| |
| const sync_pb::PrinterSpecifics& remote_printer = |
| remote_data.specifics.printer(); |
| |
| if (local_printer.updated_timestamp() > remote_printer.updated_timestamp()) { |
| return ConflictResolution::kUseLocal; |
| } |
| |
| return ConflictResolution::kUseRemote; |
| } |
| |
| 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(); |
| MergePrinterToSpecifics(*SpecificsToPrinter(*printer), merged); |
| merged->set_updated_timestamp( |
| base::Time::Now().InMillisecondsSinceUnixEpoch()); |
| CommitPrinterPut(*merged); |
| |
| return false; |
| } |
| |
| bool PrintersSyncBridge::RemovePrinter(const std::string& id) { |
| DCHECK(store_delegate_->Ready()); |
| |
| std::unique_ptr<DataTypeStore::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, syncer::DeletionOrigin::Unspecified(), |
| 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; |
| } |
| |
| std::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}; |
| } |
| |
| bool PrintersSyncBridge::HasPrinter(const std::string& id) const { |
| base::AutoLock lock(data_lock_); |
| return all_data_.find(id) != all_data_.end(); |
| } |
| |
| void PrintersSyncBridge::CommitPrinterPut( |
| const sync_pb::PrinterSpecifics& printer) { |
| std::unique_ptr<DataTypeStore::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().InMillisecondsSinceUnixEpoch()); |
| |
| CommitPrinterPut(*printer); |
| auto& dest = all_data_[printer->id()]; |
| dest = std::move(printer); |
| } |
| |
| void PrintersSyncBridge::StoreSpecifics( |
| std::unique_ptr<sync_pb::PrinterSpecifics> specifics, |
| DataTypeStore::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, |
| DataTypeStore::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 ash |