// 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 #include #include "base/bind.h" #include "base/metrics/histogram_functions.h" #include "base/stl_util.h" #include "base/task/post_task.h" #include "chrome/browser/chromeos/printing/specifics_translation.h" #include "chromeos/printing/printer_configuration.h" #include "components/sync/base/report_unrecoverable_error.h" #include "components/sync/model/model_type_change_processor.h" #include "components/sync/model/mutable_data_batch.h" #include "components/sync/model_impl/client_tag_based_model_type_processor.h" #include "components/sync/protocol/model_type_state.pb.h" #include "components/sync/protocol/sync.pb.h" namespace chromeos { namespace { using syncer::ClientTagBasedModelTypeProcessor; using syncer::ConflictResolution; using syncer::EntityChange; using syncer::EntityChangeList; using syncer::EntityData; using syncer::MetadataChangeList; using syncer::ModelTypeChangeProcessor; using syncer::ModelTypeStore; std::unique_ptr CopyToEntityData( const sync_pb::PrinterSpecifics& specifics) { auto entity_data = std::make_unique(); *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()) { base::UmaHistogramBoolean("Printing.CUPS.MigratedMakeAndModel", false); return false; } specifics->set_make_and_model( chromeos::MakeAndModel(specifics->manufacturer(), specifics->model())); base::UmaHistogramBoolean("Printing.CUPS.MigratedMakeAndModel", true); 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 ModelTypeStore. class PrintersSyncBridge::StoreProxy { public: StoreProxy(PrintersSyncBridge* owner, syncer::OnceModelTypeStoreFactory 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 CreateWriteBatch() { DCHECK(store_); return store_->CreateWriteBatch(); } // Commits writes to the database and updates metadata. void Commit(std::unique_ptr batch) { DCHECK(store_); store_->CommitWriteBatch( std::move(batch), base::BindOnce(&StoreProxy::OnCommit, weak_ptr_factory_.GetWeakPtr())); owner_->NotifyPrintersUpdated(); } private: // Callback for ModelTypeStore initialization. void OnStoreCreated(const base::Optional& error, std::unique_ptr 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 base::Optional& error, std::unique_ptr record_list) { if (error) { owner_->change_processor()->ReportError(*error); return; } bool parse_error = false; { base::AutoLock lock(owner_->data_lock_); for (const ModelTypeStore::Record& r : *record_list) { auto specifics = std::make_unique(); 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, "Failed to deserialize all specifics."}); return; } // Data loaded. Load metadata. store_->ReadAllMetadata(base::BindOnce(&StoreProxy::OnReadAllMetadata, weak_ptr_factory_.GetWeakPtr())); } // Callback to handle commit errors. void OnCommit(const base::Optional& error) { if (error) { LOG(WARNING) << "Failed to commit operation to store"; owner_->change_processor()->ReportError(*error); return; } } void OnReadAllMetadata( const base::Optional& error, std::unique_ptr metadata_batch) { if (error) { owner_->change_processor()->ReportError(*error); return; } owner_->change_processor()->ModelReadyToSync(std::move(metadata_batch)); } PrintersSyncBridge* owner_; std::unique_ptr store_; base::WeakPtrFactory weak_ptr_factory_{this}; }; PrintersSyncBridge::PrintersSyncBridge( syncer::OnceModelTypeStoreFactory callback, base::RepeatingClosure error_callback) : ModelTypeSyncBridge(std::make_unique( syncer::PRINTERS, std::move(error_callback))), store_delegate_(std::make_unique(this, std::move(callback))), observers_(new base::ObserverListThreadSafe()) {} PrintersSyncBridge::~PrintersSyncBridge() = default; std::unique_ptr PrintersSyncBridge::CreateMetadataChangeList() { return ModelTypeStore::WriteBatch::CreateMetadataChangeList(); } base::Optional PrintersSyncBridge::MergeSyncData( std::unique_ptr metadata_change_list, syncer::EntityChangeList entity_data) { DCHECK(change_processor()->IsTrackingMetadata()); std::unique_ptr batch = store_delegate_->CreateWriteBatch(); std::set 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(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; // TODO(crbug.com/737809): Remove when all data is expected to have been // migrated. bool migrated = MigrateMakeAndModel(entry.second.get()); // TODO(crbug.com/987869): Remove when all data is expected to have been // resolved. 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 {}; } base::Optional PrintersSyncBridge::ApplySyncChanges( std::unique_ptr metadata_change_list, EntityChangeList entity_changes) { std::unique_ptr batch = store_delegate_->CreateWriteBatch(); { base::AutoLock lock(data_lock_); // For all the entities from the server, apply changes. for (const std::unique_ptr& 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(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 {}; } void PrintersSyncBridge::GetData(StorageKeyList storage_keys, DataCallback callback) { auto batch = std::make_unique(); { 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)); } } } std::move(callback).Run(std::move(batch)); } void PrintersSyncBridge::GetAllDataForDebugging(DataCallback callback) { auto batch = std::make_unique(); { base::AutoLock lock(data_lock_); for (const auto& entry : all_data_) { batch->Put(entry.first, CopyToEntityData(*entry.second)); } } std::move(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 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 printer) { { base::AutoLock lock(data_lock_); AddPrinterLocked(std::move(printer)); } NotifyPrintersUpdated(); } bool PrintersSyncBridge::UpdatePrinter( std::unique_ptr printer) { bool res; { base::AutoLock lock(data_lock_); res = UpdatePrinterLocked(std::move(printer)); } NotifyPrintersUpdated(); return res; } bool PrintersSyncBridge::UpdatePrinterLocked( std::unique_ptr 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().ToJavaTime()); CommitPrinterPut(*merged); return false; } bool PrintersSyncBridge::RemovePrinter(const std::string& id) { DCHECK(store_delegate_->Ready()); std::unique_ptr 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 PrintersSyncBridge::GetAllPrinters() const { base::AutoLock lock(data_lock_); std::vector printers; for (auto& entry : all_data_) { printers.push_back(*entry.second); } return printers; } base::Optional 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 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 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 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