chromium/components/component_updater/ash/fake_component_manager_ash.cc

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

#include "components/component_updater/ash/fake_component_manager_ash.h"

#include <utility>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/version.h"

namespace component_updater {

FakeComponentManagerAsh::ComponentInfo::ComponentInfo(
    Error load_response,
    const base::FilePath& install_path,
    const base::FilePath& mount_path)
    : load_response(load_response),
      install_path(install_path),
      mount_path(mount_path) {
  // If component load fails, neither install nor mount path should be set.
  DCHECK(load_response == Error::NONE ||
         (install_path.empty() && mount_path.empty()));
  // Component should have install path set if it's expected to be loaded.
  DCHECK(load_response != Error::NONE || (!install_path.empty()));
}

FakeComponentManagerAsh::ComponentInfo::ComponentInfo(
    Error load_response,
    const base::FilePath& install_path,
    const base::FilePath& mount_path,
    const base::Version& version)
    : load_response(load_response),
      install_path(install_path),
      mount_path(mount_path),
      version(version) {
  // If component load fails, neither install nor mount path should be set and
  // version should be invalid.
  DCHECK(load_response == Error::NONE ||
         (install_path.empty() && mount_path.empty() && !version.IsValid()));
  // Component should have install path and version set if it's expected to be
  // loaded.
  DCHECK(load_response != Error::NONE ||
         (!install_path.empty() && version.IsValid()));
}

FakeComponentManagerAsh::ComponentInfo::ComponentInfo(
    const FakeComponentManagerAsh::ComponentInfo& other) = default;

FakeComponentManagerAsh::ComponentInfo&
FakeComponentManagerAsh::ComponentInfo::operator=(
    const FakeComponentManagerAsh::ComponentInfo& other) = default;

FakeComponentManagerAsh::ComponentInfo::~ComponentInfo() = default;

FakeComponentManagerAsh::FakeComponentManagerAsh() = default;

FakeComponentManagerAsh::~FakeComponentManagerAsh() = default;

bool FakeComponentManagerAsh::FinishLoadRequest(
    const std::string& name,
    const ComponentInfo& component_info) {
  if (!pending_loads_.count(name) || pending_loads_[name].empty()) {
    LOG(ERROR) << "No pending load for " << name;
    return false;
  }

  auto& pending_load = pending_loads_[name].front();
  FinishComponentLoad(name, pending_load.mount_requested, component_info);

  LoadCallback callback = std::move(pending_load.callback);
  pending_loads_[name].pop_front();

  std::move(callback).Run(component_info.load_response,
                          component_info.load_response == Error::NONE
                              ? component_info.mount_path
                              : base::FilePath());
  return true;
}

bool FakeComponentManagerAsh::ResetComponentState(const std::string& name,
                                                   const ComponentInfo& state) {
  if (!supported_components_.count(name)) {
    return false;
  }

  installed_components_.erase(name);
  mounted_components_.erase(name);

  component_infos_.erase(name);
  component_infos_.emplace(name, ComponentInfo(state));
  return true;
}

bool FakeComponentManagerAsh::HasPendingInstall(
    const std::string& name) const {
  DCHECK(queue_load_requests_);

  const auto& it = pending_loads_.find(name);
  return it != pending_loads_.end() && !it->second.empty();
}

bool FakeComponentManagerAsh::UpdateRequested(const std::string& name) const {
  DCHECK(queue_load_requests_);

  const auto& it = pending_loads_.find(name);
  return it != pending_loads_.end() && !it->second.empty() &&
         it->second.front().needs_update;
}

void FakeComponentManagerAsh::SetDelegate(Delegate* delegate) {
  // No-op, not used by the fake.
}

void FakeComponentManagerAsh::Load(const std::string& name,
                                    MountPolicy mount_policy,
                                    UpdatePolicy update_policy,
                                    LoadCallback load_callback) {
  if (!supported_components_.count(name)) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(load_callback),
                                  Error::UNKNOWN_COMPONENT, base::FilePath()));
    return;
  }

  bool needs_update = update_policy == UpdatePolicy::kForce ||
                      (!installed_components_.count(name) &&
                       update_policy != UpdatePolicy::kSkip);

  // The request has to be handled if the component is not yet installed, or it
  // requires immediate update.
  if (needs_update || !installed_components_.count(name)) {
    HandlePendingRequest(name, mount_policy == MountPolicy::kMount,
                         needs_update, std::move(load_callback));
    return;
  }

  // Handle request if the component has yet to be mounted - e.g. if previous
  // loads installed the component without mounting it.
  if (!mounted_components_.count(name) && mount_policy == MountPolicy::kMount) {
    HandlePendingRequest(name, true /*mount_requested*/, false /*needs_update*/,
                         std::move(load_callback));
    return;
  }

  // The component has been prevoiusly installed, and mounted as required by
  // this load request - run the callback according to the existing state.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(load_callback), Error::NONE,
                                mount_policy == MountPolicy::kMount
                                    ? mounted_components_[name]
                                    : base::FilePath()));
}

bool FakeComponentManagerAsh::Unload(const std::string& name) {
  {
    base::AutoLock lock(registered_components_lock_);
    registered_components_.erase(name);
  }
  mounted_components_.erase(name);
  installed_components_.erase(name);
  return unload_component_result_;
}

void FakeComponentManagerAsh::RegisterCompatiblePath(
    const std::string& name,
    CompatibleComponentInfo info) {
  installed_components_[name] = std::move(info);
}

void FakeComponentManagerAsh::UnregisterCompatiblePath(
    const std::string& name) {
  installed_components_.erase(name);
}

base::FilePath FakeComponentManagerAsh::GetCompatiblePath(
    const std::string& name) const {
  const auto& it = installed_components_.find(name);
  if (it == installed_components_.end()) {
    return base::FilePath();
  }
  return it->second.path;
}

void FakeComponentManagerAsh::SetRegisteredComponents(
    const std::set<std::string>& components) {
  base::AutoLock lock(registered_components_lock_);
  registered_components_ = components;
}

bool FakeComponentManagerAsh::IsRegisteredMayBlock(const std::string& name) {
  base::AutoLock lock(registered_components_lock_);
  return registered_components_.count(name);
}

void FakeComponentManagerAsh::RegisterInstalled() {
  NOTIMPLEMENTED();
}

void FakeComponentManagerAsh::GetVersion(
    const std::string& name,
    base::OnceCallback<void(const base::Version&)> version_callback) const {
  const auto& component_info = component_infos_.find(name);
  if (component_info == component_infos_.end() ||
      !component_info->second.version.has_value()) {
    std::move(version_callback).Run(base::Version());
    return;
  }

  std::move(version_callback).Run(component_info->second.version.value());
}

FakeComponentManagerAsh::LoadRequest::LoadRequest(bool mount_requested,
                                                   bool needs_update,
                                                   LoadCallback callback)
    : mount_requested(mount_requested),
      needs_update(needs_update),
      callback(std::move(callback)) {}

FakeComponentManagerAsh::LoadRequest::~LoadRequest() = default;

void FakeComponentManagerAsh::HandlePendingRequest(const std::string& name,
                                                    bool mount_requested,
                                                    bool needs_update,
                                                    LoadCallback callback) {
  if (queue_load_requests_) {
    pending_loads_[name].emplace_back(mount_requested, needs_update,
                                      std::move(callback));
    return;
  }

  const auto& component_info = component_infos_.find(name);
  if (component_info == component_infos_.end()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), Error::INSTALL_FAILURE,
                                  base::FilePath()));
    return;
  }

  FinishComponentLoad(name, mount_requested, component_info->second);

  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), component_info->second.load_response,
                     component_info->second.mount_path));
}

void FakeComponentManagerAsh::FinishComponentLoad(
    const std::string& name,
    bool mount_requested,
    const ComponentInfo& component_info) {
  {
    base::AutoLock lock(registered_components_lock_);
    registered_components_.insert(name);
  }

  if (component_info.load_response != Error::NONE) {
    return;
  }

  DCHECK_EQ(mount_requested, !component_info.mount_path.empty());
  installed_components_[name] = CompatibleComponentInfo(
      component_info.install_path, component_info.version);
  if (mount_requested) {
    mounted_components_[name] = component_info.mount_path;
  }
}

}  // namespace component_updater