chromium/chromeos/ash/components/dbus/os_install/os_install_client.cc

// Copyright 2021 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/os_install/os_install_client.h"

#include <optional>

#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "chromeos/ash/components/dbus/os_install/fake_os_install_client.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {
namespace {

OsInstallClient* g_instance = nullptr;

std::optional<OsInstallClient::Status> ParseStatus(const std::string& str) {
  if (str == os_install_service::kStatusInProgress)
    return OsInstallClient::Status::InProgress;
  if (str == os_install_service::kStatusSucceeded)
    return OsInstallClient::Status::Succeeded;
  if (str == os_install_service::kStatusFailed)
    return OsInstallClient::Status::Failed;
  if (str == os_install_service::kStatusNoDestinationDeviceFound)
    return OsInstallClient::Status::NoDestinationDeviceFound;

  LOG(ERROR) << "Invalid status: " << str;
  return std::nullopt;
}

class OsInstallClientImpl : public OsInstallClient {
 public:
  OsInstallClientImpl() = default;
  ~OsInstallClientImpl() override = default;
  OsInstallClientImpl(const OsInstallClientImpl&) = delete;
  OsInstallClientImpl& operator=(const OsInstallClientImpl&) = delete;

  void Init(dbus::Bus* bus);

  void AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer) override;
  bool HasObserver(const Observer* observer) const override;
  TestInterface* GetTestInterface() override;
  void StartOsInstall() override;

 private:
  void NotifyStatusChanged(std::optional<Status> status,
                           const std::string& service_log);
  void HandleStartResponse(dbus::Response* response);
  void StatusUpdateReceived(dbus::Signal* signal);
  void StatusUpdateConnected(const std::string& interface_name,
                             const std::string& signal_name,
                             bool success);

  raw_ptr<dbus::ObjectProxy> proxy_ = nullptr;
  base::ObserverList<Observer> observers_;

  base::WeakPtrFactory<OsInstallClientImpl> weak_factory_{this};
};

void OsInstallClientImpl::Init(dbus::Bus* bus) {
  proxy_ = bus->GetObjectProxy(
      os_install_service::kOsInstallServiceServiceName,
      dbus::ObjectPath(os_install_service::kOsInstallServiceServicePath));

  proxy_->ConnectToSignal(
      os_install_service::kOsInstallServiceInterface,
      os_install_service::kSignalOsInstallStatusChanged,
      base::BindRepeating(&OsInstallClientImpl::StatusUpdateReceived,
                          weak_factory_.GetWeakPtr()),
      base::BindOnce(&OsInstallClientImpl::StatusUpdateConnected,
                     weak_factory_.GetWeakPtr()));
}

void OsInstallClientImpl::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void OsInstallClientImpl::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool OsInstallClientImpl::HasObserver(const Observer* observer) const {
  return observers_.HasObserver(observer);
}

OsInstallClient::TestInterface* OsInstallClientImpl::GetTestInterface() {
  return nullptr;
}

void OsInstallClientImpl::StartOsInstall() {
  dbus::MethodCall method_call(os_install_service::kOsInstallServiceInterface,
                               os_install_service::kMethodStartOsInstall);
  proxy_->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                     base::BindOnce(&OsInstallClientImpl::HandleStartResponse,
                                    weak_factory_.GetWeakPtr()));
}

void OsInstallClientImpl::NotifyStatusChanged(std::optional<Status> status,
                                              const std::string& service_log) {
  if (!status) {
    status = Status::Failed;
  }

  for (auto& observer : observers_) {
    observer.StatusChanged(*status, service_log);
  }
}

void OsInstallClientImpl::HandleStartResponse(dbus::Response* response) {
  if (!response) {
    LOG(ERROR) << "Invalid response";
    NotifyStatusChanged(Status::Failed, /*service_log=*/"");
    return;
  }

  dbus::MessageReader reader(response);
  std::string status_str;
  if (!reader.PopString(&status_str)) {
    LOG(ERROR) << "Missing status";
    NotifyStatusChanged(Status::Failed, /*service_log=*/"");
    return;
  }

  NotifyStatusChanged(ParseStatus(status_str), /*service_log=*/"");
}

void OsInstallClientImpl::StatusUpdateReceived(dbus::Signal* signal) {
  dbus::MessageReader reader(signal);

  // Read and parse the status.
  std::string status_str;
  if (!reader.PopString(&status_str)) {
    LOG(ERROR) << "Missing status";
    return;
  }
  const auto status = ParseStatus(status_str);
  if (!status) {
    return;
  }

  // Read the service log.
  std::string service_log;
  if (!reader.PopString(&service_log)) {
    LOG(ERROR) << "Missing service_log";
    return;
  }

  NotifyStatusChanged(*status, service_log);
}

void OsInstallClientImpl::StatusUpdateConnected(
    const std::string& interface_name,
    const std::string& signal_name,
    bool success) {
  LOG_IF(WARNING, !success) << "Failed to connect to status updated signal.";
}

}  // namespace

OsInstallClient::OsInstallClient() {
  CHECK(!g_instance);
  g_instance = this;
}

OsInstallClient::~OsInstallClient() {
  CHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

// static
void OsInstallClient::Initialize(dbus::Bus* bus) {
  CHECK(bus);
  (new OsInstallClientImpl())->Init(bus);
}

// static
void OsInstallClient::InitializeFake() {
  new FakeOsInstallClient();
}

// static
void OsInstallClient::Shutdown() {
  CHECK(g_instance);
  delete g_instance;
  // The destructor resets |g_instance|.
  DCHECK(!g_instance);
}

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

}  // namespace ash