chromium/chromeos/ash/components/dbus/printscanmgr/printscanmgr_client.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/dbus/printscanmgr/printscanmgr_client.h"

#include <stdint.h>

#include <dbus/dbus-protocol.h>

#include <map>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "chromeos/ash/components/dbus/printscanmgr/fake_printscanmgr_client.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {

PrintscanmgrClient* g_instance = nullptr;

printscanmgr::AddPrinterResult DBusErrorFromString(
    const std::string& dbus_error_string) {
  static const base::NoDestructor<
      std::map<std::string, printscanmgr::AddPrinterResult>>
      error_string_map({
          {DBUS_ERROR_NO_REPLY,
           printscanmgr::AddPrinterResult::ADD_PRINTER_RESULT_DBUS_NO_REPLY},
          {DBUS_ERROR_TIMEOUT,
           printscanmgr::AddPrinterResult::ADD_PRINTER_RESULT_DBUS_TIMEOUT},
      });

  auto it = error_string_map->find(dbus_error_string);
  return it != error_string_map->end()
             ? it->second
             : printscanmgr::AddPrinterResult::ADD_PRINTER_RESULT_DBUS_GENERIC;
}

// The PrintscanmgrClient implementation used in production.
class PrintscanmgrClientImpl : public PrintscanmgrClient {
 public:
  PrintscanmgrClientImpl() = default;
  PrintscanmgrClientImpl(const PrintscanmgrClientImpl&) = delete;
  PrintscanmgrClientImpl& operator=(const PrintscanmgrClientImpl&) = delete;
  ~PrintscanmgrClientImpl() override = default;

  // DBusClient overrides:
  void Init(dbus::Bus* bus) override {
    printscanmgr_proxy_ = bus->GetObjectProxy(
        printscanmgr::kPrintscanmgrServiceName,
        dbus::ObjectPath(printscanmgr::kPrintscanmgrServicePath));
  }

  // PrintscanmgrClient overrides:
  void CupsAddManuallyConfiguredPrinter(
      const printscanmgr::CupsAddManuallyConfiguredPrinterRequest& request,
      chromeos::DBusMethodCallback<
          printscanmgr::CupsAddManuallyConfiguredPrinterResponse> callback)
      override {
    dbus::MethodCall method_call(
        printscanmgr::kPrintscanmgrInterface,
        printscanmgr::kCupsAddManuallyConfiguredPrinter);
    dbus::MessageWriter writer(&method_call);
    if (!writer.AppendProtoAsArrayOfBytes(request)) {
      LOG(ERROR) << "Failed to encode CupsAddManuallyConfiguredPrinterRequest "
                    "protobuf.";
      printscanmgr::CupsAddManuallyConfiguredPrinterResponse response;
      response.set_result(printscanmgr::AddPrinterResult::
                              ADD_PRINTER_RESULT_DBUS_ENCODING_FAILURE);
      std::move(callback).Run(std::move(response));
      return;
    }

    printscanmgr_proxy_->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(
            &PrintscanmgrClientImpl::OnPrinterAdded<
                printscanmgr::CupsAddManuallyConfiguredPrinterResponse>,
            weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void CupsAddAutoConfiguredPrinter(
      const printscanmgr::CupsAddAutoConfiguredPrinterRequest& request,
      chromeos::DBusMethodCallback<
          printscanmgr::CupsAddAutoConfiguredPrinterResponse> callback)
      override {
    dbus::MethodCall method_call(printscanmgr::kPrintscanmgrInterface,
                                 printscanmgr::kCupsAddAutoConfiguredPrinter);
    dbus::MessageWriter writer(&method_call);
    if (!writer.AppendProtoAsArrayOfBytes(request)) {
      LOG(ERROR) << "Failed to encode CupsAddAutoConfiguredPrinterRequest "
                    "protobuf.";
      printscanmgr::CupsAddAutoConfiguredPrinterResponse response;
      response.set_result(printscanmgr::AddPrinterResult::
                              ADD_PRINTER_RESULT_DBUS_ENCODING_FAILURE);
      std::move(callback).Run(std::move(response));
      return;
    }

    printscanmgr_proxy_->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PrintscanmgrClientImpl::OnPrinterAdded<
                           printscanmgr::CupsAddAutoConfiguredPrinterResponse>,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void CupsRemovePrinter(
      const printscanmgr::CupsRemovePrinterRequest& request,
      chromeos::DBusMethodCallback<printscanmgr::CupsRemovePrinterResponse>
          callback,
      base::OnceClosure error_callback) override {
    dbus::MethodCall method_call(printscanmgr::kPrintscanmgrInterface,
                                 printscanmgr::kCupsRemovePrinter);
    dbus::MessageWriter writer(&method_call);
    if (!writer.AppendProtoAsArrayOfBytes(request)) {
      LOG(ERROR) << "Failed to encode CupsRemovePrinterRequest protobuf.";
      std::move(error_callback).Run();
      return;
    }

    printscanmgr_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PrintscanmgrClientImpl::OnPrinterRemoved,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                       std::move(error_callback)));
  }

  void CupsRetrievePrinterPpd(
      const printscanmgr::CupsRetrievePpdRequest& request,
      chromeos::DBusMethodCallback<printscanmgr::CupsRetrievePpdResponse>
          callback,
      base::OnceClosure error_callback) override {
    dbus::MethodCall method_call(printscanmgr::kPrintscanmgrInterface,
                                 printscanmgr::kCupsRetrievePpd);
    dbus::MessageWriter writer(&method_call);
    if (!writer.AppendProtoAsArrayOfBytes(request)) {
      LOG(ERROR) << "Failed to encode CupsRetrievePpdRequest protobuf.";
      std::move(error_callback).Run();
      return;
    }

    printscanmgr_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&PrintscanmgrClientImpl::OnRetrievedPrinterPpd,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                       std::move(error_callback)));
  }

 private:
  template <typename T>
  void OnPrinterAdded(chromeos::DBusMethodCallback<T> callback,
                      dbus::Response* response,
                      dbus::ErrorResponse* err_response) {
    if (!response) {
      SendErrorResponse(std::move(callback), err_response);
      return;
    }

    T result;
    if (!dbus::MessageReader(response).PopArrayOfBytesAsProto(&result)) {
      SendErrorResponse(std::move(callback), err_response);
      return;
    }

    DCHECK_GE(result.result(), 0);
    std::move(callback).Run(result);
  }

  void OnPrinterRemoved(chromeos::DBusMethodCallback<
                            printscanmgr::CupsRemovePrinterResponse> callback,
                        base::OnceClosure error_callback,
                        dbus::Response* response) {
    if (!response) {
      std::move(error_callback).Run();
      return;
    }

    printscanmgr::CupsRemovePrinterResponse result;
    if (!dbus::MessageReader(response).PopArrayOfBytesAsProto(&result)) {
      std::move(error_callback).Run();
      return;
    }

    std::move(callback).Run(result);
  }

  void OnRetrievedPrinterPpd(
      chromeos::DBusMethodCallback<printscanmgr::CupsRetrievePpdResponse>
          callback,
      base::OnceClosure error_callback,
      dbus::Response* response) {
    printscanmgr::CupsRetrievePpdResponse result;
    if (!(response &&
          dbus::MessageReader(response).PopArrayOfBytesAsProto(&result))) {
      LOG(ERROR) << "Failed to retrieve printer PPD";
      std::move(error_callback).Run();
      return;
    }

    std::move(callback).Run(result);
  }

  template <typename T>
  void SendErrorResponse(chromeos::DBusMethodCallback<T> callback,
                         dbus::ErrorResponse* err_response) {
    printscanmgr::AddPrinterResult dbus_error =
        printscanmgr::AddPrinterResult::ADD_PRINTER_RESULT_DBUS_GENERIC;
    if (err_response) {
      dbus::MessageReader err_reader(err_response);
      std::string err_str = err_response->GetErrorName();
      dbus_error = DBusErrorFromString(err_str);
    }

    T response;
    response.set_result(dbus_error);
    std::move(callback).Run(response);
  }

  raw_ptr<dbus::ObjectProxy, LeakedDanglingUntriaged> printscanmgr_proxy_ =
      nullptr;
  base::WeakPtrFactory<PrintscanmgrClientImpl> weak_ptr_factory_{this};
};

}  // namespace

// static
PrintscanmgrClient* PrintscanmgrClient::Get() {
  return g_instance;
}

// static
void PrintscanmgrClient::Initialize(dbus::Bus* bus) {
  CHECK(bus);
  CHECK(!g_instance);
  g_instance = new PrintscanmgrClientImpl();
  g_instance->Init(bus);
}

// static
void PrintscanmgrClient::InitializeFake() {
  CHECK(!g_instance);
  g_instance = new FakePrintscanmgrClient();
  g_instance->Init(nullptr);
}

// static
void PrintscanmgrClient::InitializeFakeForTest() {
  g_instance = new FakePrintscanmgrClient();
  g_instance->Init(nullptr);
}

// static
void PrintscanmgrClient::Shutdown() {
  CHECK(g_instance);
  delete g_instance;
  g_instance = nullptr;
}

PrintscanmgrClient::PrintscanmgrClient() = default;
PrintscanmgrClient::~PrintscanmgrClient() = default;

}  // namespace ash