chromium/chrome/browser/ash/printing/printers_sync_bridge.cc

// 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, "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 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) {
  // 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<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