chromium/chrome/browser/ash/printing/oauth2/profile_auth_servers_sync_bridge.cc

// Copyright 2022 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/oauth2/profile_auth_servers_sync_bridge.h"

#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/trace_event.h"
#include "chrome/common/channel_info.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/data_type_store.h"
#include "components/sync/model/data_type_store_base.h"
#include "components/sync/model/data_type_sync_bridge.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/model_error.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/entity_data.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/printers_authorization_server_specifics.pb.h"
#include "url/gurl.h"

namespace ash::printing::oauth2 {

namespace {

sync_pb::PrintersAuthorizationServerSpecifics ToSpecifics(
    const std::string& uri) {
  sync_pb::PrintersAuthorizationServerSpecifics specifics;
  specifics.set_uri(uri);
  return specifics;
}

std::unique_ptr<syncer::EntityData> ToEntityDataPtr(const std::string& uri) {
  auto entity_data = std::make_unique<syncer::EntityData>();
  *entity_data->specifics.mutable_printers_authorization_server() =
      ToSpecifics(uri);
  entity_data->name = uri;
  return entity_data;
}

std::set<GURL> ToSetOfUris(const std::set<std::string>& strs) {
  std::set<GURL> uris;
  for (const std::string& str : strs) {
    GURL uri(str);
    if (!uri.is_valid()) {
      LOG(WARNING) << "Failed to parse URI in ProfileAuthServersSyncBridge";
      continue;
    }
    uris.insert(std::move(uri));
  }
  return uris;
}

}  // namespace

std::unique_ptr<ProfileAuthServersSyncBridge>
ProfileAuthServersSyncBridge::Create(
    Observer* observer,
    syncer::OnceDataTypeStoreFactory store_factory) {
  DCHECK(observer);
  return base::WrapUnique(new ProfileAuthServersSyncBridge(
      std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
          syncer::PRINTERS_AUTHORIZATION_SERVERS,
          base::BindRepeating(&syncer::ReportUnrecoverableError,
                              chrome::GetChannel())),
      std::move(store_factory), observer));
}

std::unique_ptr<ProfileAuthServersSyncBridge>
ProfileAuthServersSyncBridge::CreateForTesting(
    Observer* observer,
    std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor,
    syncer::OnceDataTypeStoreFactory store_factory) {
  DCHECK(observer);
  DCHECK(change_processor);
  return base::WrapUnique(new ProfileAuthServersSyncBridge(
      std::move(change_processor), std::move(store_factory), observer));
}

ProfileAuthServersSyncBridge::~ProfileAuthServersSyncBridge() = default;

void ProfileAuthServersSyncBridge::AddAuthorizationServer(
    const GURL& server_uri) {
  DCHECK(initialization_completed_);
  const std::string key = server_uri.spec();
  DCHECK(!key.empty());
  servers_uris_.insert(key);
  auto batch = store_->CreateWriteBatch();
  batch->WriteData(key, ToSpecifics(key).SerializeAsString());
  if (change_processor()->IsTrackingMetadata()) {
    change_processor()->Put(key, ToEntityDataPtr(key),
                            batch->GetMetadataChangeList());
  }
  store_->CommitWriteBatch(
      std::move(batch), base::BindOnce(&ProfileAuthServersSyncBridge::OnCommit,
                                       weak_ptr_factory_.GetWeakPtr()));
}

ProfileAuthServersSyncBridge::ProfileAuthServersSyncBridge(
    std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor,
    syncer::OnceDataTypeStoreFactory store_factory,
    Observer* observer)
    : syncer::DataTypeSyncBridge(std::move(change_processor)),
      observer_(observer) {
  std::move(store_factory)
      .Run(syncer::PRINTERS_AUTHORIZATION_SERVERS,
           base::BindOnce(&ProfileAuthServersSyncBridge::OnStoreCreated,
                          weak_ptr_factory_.GetWeakPtr()));
}

void ProfileAuthServersSyncBridge::OnStoreCreated(
    const std::optional<syncer::ModelError>& error,
    std::unique_ptr<syncer::DataTypeStore> store) {
  if (error) {
    change_processor()->ReportError(*error);
    return;
  }

  store_ = std::move(store);
  store_->ReadAllData(
      base::BindOnce(&ProfileAuthServersSyncBridge::OnReadAllData,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ProfileAuthServersSyncBridge::OnReadAllData(
    const std::optional<syncer::ModelError>& error,
    std::unique_ptr<syncer::DataTypeStore::RecordList> record_list) {
  if (error) {
    change_processor()->ReportError(*error);
    return;
  }

  for (const syncer::DataTypeStore::Record& r : *record_list) {
    sync_pb::PrintersAuthorizationServerSpecifics specifics;
    if (!specifics.ParseFromString(r.value)) {
      change_processor()->ReportError(
          {FROM_HERE, "Failed to deserialize all specifics."});
      return;
    }
    servers_uris_.insert(specifics.uri());
  }

  // Data loaded. Notify the observer and load metadata.
  NotifyObserver(servers_uris_, /*deleted=*/{});
  store_->ReadAllMetadata(
      base::BindOnce(&ProfileAuthServersSyncBridge::OnReadAllMetadata,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ProfileAuthServersSyncBridge::OnReadAllMetadata(
    const std::optional<syncer::ModelError>& error,
    std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
  TRACE_EVENT0("ui", "ProfileAuthServersSyncBridge::OnReadAllMetadata");
  if (error) {
    change_processor()->ReportError(*error);
    return;
  }
  change_processor()->ModelReadyToSync(std::move(metadata_batch));
  initialization_completed_ = true;
  observer_->OnProfileAuthorizationServersInitialized();
}

std::unique_ptr<syncer::MetadataChangeList>
ProfileAuthServersSyncBridge::CreateMetadataChangeList() {
  return syncer::DataTypeStore::WriteBatch::CreateMetadataChangeList();
}

std::optional<syncer::ModelError>
ProfileAuthServersSyncBridge::MergeFullSyncData(
    std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
    syncer::EntityChangeList entity_data) {
  // Every local URI is considered unsynced until the contrary is proven, i.e.
  // until the same URI is seen in the incoming `entity_data`.
  std::set<std::string> unsynced_local_uris = servers_uris_;
  std::set<std::string> added_local_uris;
  std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
      store_->CreateWriteBatch();

  for (const std::unique_ptr<syncer::EntityChange>& change : entity_data) {
    const sync_pb::PrintersAuthorizationServerSpecifics& specifics =
        change->data().specifics.printers_authorization_server();
    const std::string& remote_uri = specifics.uri();
    DCHECK_EQ(change->storage_key(), remote_uri);
    auto [unused, is_new] = servers_uris_.insert(remote_uri);
    if (is_new) {
      added_local_uris.insert(remote_uri);
      batch->WriteData(remote_uri, specifics.SerializeAsString());
    } else {
      unsynced_local_uris.erase(remote_uri);
    }
  }

  // Send unmatched local URIs to the server.
  for (const std::string& uri : unsynced_local_uris) {
    change_processor()->Put(uri, ToEntityDataPtr(uri),
                            metadata_change_list.get());
  }

  // Save new local URIs to the local store.
  batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
  store_->CommitWriteBatch(
      std::move(batch), base::BindOnce(&ProfileAuthServersSyncBridge::OnCommit,
                                       weak_ptr_factory_.GetWeakPtr()));

  NotifyObserver(added_local_uris, /*deleted=*/{});
  return std::nullopt;
}

std::optional<syncer::ModelError>
ProfileAuthServersSyncBridge::ApplyIncrementalSyncChanges(
    std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
    syncer::EntityChangeList entity_changes) {
  std::set<std::string> added_local_uris;
  std::set<std::string> deleted_local_uris;

  std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
      store_->CreateWriteBatch();
  for (const std::unique_ptr<syncer::EntityChange>& change : entity_changes) {
    const std::string& uri = change->storage_key();
    if (change->type() == syncer::EntityChange::ACTION_DELETE) {
      if (servers_uris_.erase(uri)) {
        batch->DeleteData(uri);
        deleted_local_uris.insert(uri);
      }
    } else {
      if (servers_uris_.insert(uri).second) {  // true <=> uri wasn't there
        batch->WriteData(uri, change->data()
                                  .specifics.printers_authorization_server()
                                  .SerializeAsString());
        added_local_uris.insert(uri);
      }
    }
  }
  batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
  store_->CommitWriteBatch(
      std::move(batch), base::BindOnce(&ProfileAuthServersSyncBridge::OnCommit,
                                       weak_ptr_factory_.GetWeakPtr()));

  NotifyObserver(added_local_uris, deleted_local_uris);
  return std::nullopt;
}

std::unique_ptr<syncer::DataBatch>
ProfileAuthServersSyncBridge::GetDataForCommit(StorageKeyList storage_keys) {
  auto batch = std::make_unique<syncer::MutableDataBatch>();
  for (const std::string& key : storage_keys) {
    if (base::Contains(servers_uris_, key)) {
      batch->Put(key, ToEntityDataPtr(key));
    }
  }
  return batch;
}

std::unique_ptr<syncer::DataBatch>
ProfileAuthServersSyncBridge::GetAllDataForDebugging() {
  auto batch = std::make_unique<syncer::MutableDataBatch>();
  for (const auto& uri : servers_uris_) {
    batch->Put(uri, ToEntityDataPtr(uri));
  }
  return batch;
}

std::string ProfileAuthServersSyncBridge::GetClientTag(
    const syncer::EntityData& entity_data) {
  return GetStorageKey(entity_data);
}

std::string ProfileAuthServersSyncBridge::GetStorageKey(
    const syncer::EntityData& entity_data) {
  DCHECK(entity_data.specifics.has_printers_authorization_server());
  return entity_data.specifics.printers_authorization_server().uri();
}

void ProfileAuthServersSyncBridge::OnCommit(
    const std::optional<syncer::ModelError>& error) {
  if (error) {
    LOG(WARNING) << "Failed to commit operation to store in "
                    "ProfileAuthServersSyncBridge";
    change_processor()->ReportError(*error);
  }
}

void ProfileAuthServersSyncBridge::NotifyObserver(
    const std::set<std::string>& added,
    const std::set<std::string>& deleted) {
  // Convert std::set<std::string> to std::set<GURL>.
  std::set<GURL> added_uris = ToSetOfUris(added);
  std::set<GURL> deleted_uris = ToSetOfUris(deleted);
  // Call the observer.
  if (!added_uris.empty() || !deleted_uris.empty()) {
    observer_->OnProfileAuthorizationServersUpdate(std::move(added_uris),
                                                   std::move(deleted_uris));
  }
}

}  // namespace ash::printing::oauth2