// Copyright 2014 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/update_client/update_client.h"

#include <algorithm>
#include <queue>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/ping_manager.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/protocol_parser.h"
#include "components/update_client/task_check_for_update.h"
#include "components/update_client/task_send_ping.h"
#include "components/update_client/task_traits.h"
#include "components/update_client/task_update.h"
#include "components/update_client/update_checker.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/update_client_internal.h"
#include "components/update_client/update_engine.h"
#include "components/update_client/utils.h"
#include "url/gurl.h"

namespace update_client {

CrxInstaller::InstallParams::InstallParams(
    const std::string& run,
    const std::string& arguments,
    const std::string& server_install_data)
    : run(run),
      arguments(arguments),
      server_install_data(server_install_data) {}

CrxUpdateItem::CrxUpdateItem() : state(ComponentState::kNew) {}
CrxUpdateItem::CrxUpdateItem(const CrxUpdateItem& other) = default;
CrxUpdateItem& CrxUpdateItem::operator=(const CrxUpdateItem& other) = default;
CrxUpdateItem::~CrxUpdateItem() = default;

CrxComponent::CrxComponent() = default;
CrxComponent::CrxComponent(const CrxComponent& other) = default;
CrxComponent& CrxComponent::operator=(const CrxComponent& other) = default;
CrxComponent::~CrxComponent() = default;

// It is important that an instance of the UpdateClient binds an unretained
// pointer to itself. Otherwise, a life time circular dependency between this
// instance and its inner members prevents the destruction of this instance.
// Using unretained references is allowed in this case since the life time of
// the UpdateClient instance exceeds the life time of its inner members,
// including any sequences that might execute callbacks bound to it.
UpdateClientImpl::UpdateClientImpl(
    scoped_refptr<Configurator> config,
    scoped_refptr<PingManager> ping_manager,
    UpdateChecker::Factory update_checker_factory)
    : config_(config), ping_manager_(ping_manager) {
  update_engine_ = base::MakeRefCounted<UpdateEngine>(
      config, update_checker_factory, ping_manager_.get(),
      base::BindRepeating(&UpdateClientImpl::NotifyObservers,
                          weak_ptr_factory_.GetWeakPtr()));
}

UpdateClientImpl::~UpdateClientImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  CHECK(task_queue_.empty());

  if (!tasks_.empty()) {
    VLOG(2) << __func__ << ": found tasks running";
    for (const scoped_refptr<Task>& t : tasks_) {
      VLOG(2) << "task: " << t->name()
              << ", id: " << base::JoinString(t->ids(), ",");
    }
  }

  config_ = nullptr;
}

base::RepeatingClosure UpdateClientImpl::Install(
    const std::string& id,
    CrxDataCallback crx_data_callback,
    CrxStateChangeCallback crx_state_change_callback,
    Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (IsUpdating(id)) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), Error::UPDATE_IN_PROGRESS));
    return base::DoNothing();
  }

  // Install tasks are run concurrently in the foreground and never queued up.
  auto task = base::MakeRefCounted<TaskUpdate>(
      update_engine_.get(), /*is_foreground=*/true, /*is_install=*/true,
      std::vector<std::string>{id}, std::move(crx_data_callback),
      crx_state_change_callback,
      base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
                     std::move(callback)));
  RunTask(task);
  return base::BindRepeating(&Task::Cancel, task);
}

// Update tasks are background tasks and queued up.
void UpdateClientImpl::Update(const std::vector<std::string>& ids,
                              CrxDataCallback crx_data_callback,
                              CrxStateChangeCallback crx_state_change_callback,
                              bool is_foreground,
                              Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  RunOrEnqueueTask(base::MakeRefCounted<TaskUpdate>(
      update_engine_.get(), is_foreground, /*is_install=*/false, ids,
      std::move(crx_data_callback), crx_state_change_callback,
      base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
                     std::move(callback))));
}

// Update check tasks are queued up.
void UpdateClientImpl::CheckForUpdate(
    const std::string& id,
    CrxDataCallback crx_data_callback,
    CrxStateChangeCallback crx_state_change_callback,
    bool is_foreground,
    Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  RunOrEnqueueTask(base::MakeRefCounted<TaskCheckForUpdate>(
      update_engine_.get(), id, std::move(crx_data_callback),
      crx_state_change_callback, is_foreground,
      base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
                     std::move(callback))));
}

void UpdateClientImpl::RunTask(scoped_refptr<Task> task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  VLOG(2) << __func__;

  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&Task::Run, task));
  tasks_.insert(task);
}

void UpdateClientImpl::RunOrEnqueueTask(scoped_refptr<Task> task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (tasks_.empty()) {
    RunTask(task);
  } else {
    task_queue_.push_back(task);
  }
}

void UpdateClientImpl::OnTaskComplete(Callback callback,
                                      scoped_refptr<Task> task,
                                      Error error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(task);

  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), error));

  tasks_.erase(task);
  VLOG(2) << __func__ << ": tasks_.empty(): " << tasks_.empty()
          << ", task_queue_.empty(): " << task_queue_.empty()
          << ", error: " << static_cast<int>(error);

  if (is_stopped_) {
    return;
  }

  // Pick up a task from the queue if the queue has pending tasks and no other
  // task is running.
  if (tasks_.empty() && !task_queue_.empty()) {
    auto queued_task = task_queue_.front();
    task_queue_.pop_front();
    RunTask(queued_task);
  }
}

void UpdateClientImpl::AddObserver(Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observer_list_.AddObserver(observer);
}

void UpdateClientImpl::RemoveObserver(Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observer_list_.RemoveObserver(observer);
}

void UpdateClientImpl::NotifyObservers(const CrxUpdateItem& item) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  for (auto& observer : observer_list_) {
    observer.OnEvent(item);
  }
}

bool UpdateClientImpl::GetCrxUpdateState(const std::string& id,
                                         CrxUpdateItem* update_item) const {
  return update_engine_->GetUpdateState(id, update_item);
}

bool UpdateClientImpl::IsUpdating(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (const auto& task : tasks_) {
    if (std::ranges::contains(task->ids(), id)) {
      return true;
    }
  }

  for (const auto& task : task_queue_) {
    if (std::ranges::contains(task->ids(), id)) {
      return true;
    }
  }

  return false;
}

void UpdateClientImpl::Stop() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  is_stopped_ = true;

  // Cancel the pending tasks. These tasks are safe to cancel and delete since
  // they have not picked up by the update engine, and not shared with any
  // task runner yet.
  while (!task_queue_.empty()) {
    auto task = task_queue_.front();
    task_queue_.pop_front();
    task->Cancel();
  }

  // Also cancel active tasks to trigger downloader cleanup. Otherwise, upon the
  // browser shutdown, the resources employed by the active tasks will leak, as
  // the operating system kills the thread associated with the update engine
  // task runner.
  for (auto& task : tasks_) {
    task->Cancel();
  }
}

void UpdateClientImpl::CleanupStaleDownloads(base::Time older_than,
                                             base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!task_queue_.empty() || !tasks_.empty()) {
    VLOG(2) << __func__ << ": skipping cleanup, tasks_: " << tasks_.size()
            << ", task_queue_: " << task_queue_.size();
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(callback));
    return;
  }

  // Clean up stale downloads in the temp directory.
  base::ThreadPool::PostTaskAndReply(
      FROM_HERE, kTaskTraits,
      base::BindOnce(
          [](const base::FilePath::StringType& prod_id, base::Time older_than) {
            base::FilePath temp_dir;
#if BUILDFLAG(IS_WIN)
            if (!base::GetSecureTempDirectory(&temp_dir)) {
              return;
            }
#else   // BUILDFLAG(IS_WIN)
            if (!base::GetTempDir(&temp_dir)) {
              return;
            }
#endif  // BUILDFLAG(IS_WIN)

            CleanupDirectoriesOlderThan(
                temp_dir,
                base::StrCat(
                    {prod_id, FILE_PATH_LITERAL("_chrome_url_fetcher_*")}),
                base::Time::Now() - older_than);
          },
          update_client::UTF8ToStringType(config_->GetProdId()), older_than),
      std::move(callback));
}

void UpdateClientImpl::SendPing(const CrxComponent& crx_component,
                                PingParams ping_params,
                                Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  VLOG(2) << __func__;

  RunTask(base::MakeRefCounted<TaskSendPing>(
      update_engine_.get(), crx_component, ping_params,
      base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
                     std::move(callback))));
}

scoped_refptr<UpdateClient> UpdateClientFactory(
    scoped_refptr<Configurator> config) {
  return base::MakeRefCounted<UpdateClientImpl>(
      config, base::MakeRefCounted<PingManager>(config),
      base::BindRepeating(&UpdateChecker::Create));
}

void RegisterPrefs(PrefRegistrySimple* registry) {
  RegisterPersistedDataPrefs(registry);
}

// This function has the exact same implementation as RegisterPrefs. We have
// this implementation here to make the intention more clear that is local user
// profile access is needed.
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
  RegisterPersistedDataPrefs(registry);
}

}  // namespace update_client
