chromium/chrome/browser/ash/file_manager/io_task_controller.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 "chrome/browser/ash/file_manager/io_task_controller.h"

#include <vector>

#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "content/public/browser/device_service.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"

namespace file_manager {

namespace io_task {

constexpr auto kThrottleInterval = base::Milliseconds(200);

IOTaskController::IOTaskController() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

IOTaskController::~IOTaskController() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void IOTaskController::MaybeNotifyIOTaskObservers(
    const ProgressStatus& status) {
  auto last_update = tasks_last_update_[status.task_id];

  if (base::Time::Now() - last_update < kThrottleInterval) {
    return;
  }

  NotifyIOTaskObservers(status);
}

void IOTaskController::NotifyIOTaskObservers(const ProgressStatus& status) {
  for (IOTaskController::Observer& observer : observers_) {
    observer.OnIOTaskStatus(status);
  }
  tasks_last_update_[status.task_id] = base::Time::Now();
}

void IOTaskController::OnIOTaskProgress(const ProgressStatus& status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  MaybeNotifyIOTaskObservers(status);
}

void IOTaskController::OnIOTaskComplete(IOTaskId task_id,
                                        ProgressStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  NotifyIOTaskObservers(status);
  RemoveIOTask(task_id);
}

void IOTaskController::AddObserver(IOTaskController::Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observers_.AddObserver(observer);
}

void IOTaskController::RemoveObserver(IOTaskController::Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observers_.RemoveObserver(observer);
}

IOTaskId IOTaskController::Add(std::unique_ptr<IOTask> task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  IOTaskId task_id = ++last_id_;
  task->SetTaskID(task_id);

  // Notify observers that the task has been queued.
  NotifyIOTaskObservers(task->progress());

  // Make sure the first "in progress" event after "queued" is always sent.
  // Some listeners require at least one in progress event.
  tasks_last_update_[task_id] -= kThrottleInterval;

  // TODO(b/199807189): Queue the task.
  PutIOTask(task_id, std::move(task))
      ->Execute(base::BindRepeating(&IOTaskController::OnIOTaskProgress,
                                    weak_ptr_factory_.GetWeakPtr()),
                base::BindPostTaskToCurrentDefault(
                    base::BindOnce(&IOTaskController::OnIOTaskComplete,
                                   weak_ptr_factory_.GetWeakPtr(), task_id)));
  return task_id;
}

void IOTaskController::Pause(IOTaskId task_id, PauseParams params) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto it = tasks_.find(task_id);
  if (it != tasks_.end()) {
    IOTask* task = it->second.get();
    task->Pause(std::move(params));
    NotifyIOTaskObservers(task->progress());
  } else {
    LOG(WARNING) << "Failed to pause task: " << task_id << " not found";
  }
}

void IOTaskController::Resume(IOTaskId task_id, ResumeParams params) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto it = tasks_.find(task_id);
  if (it != tasks_.end()) {
    IOTask* task = it->second.get();
    task->Resume(std::move(params));
    NotifyIOTaskObservers(task->progress());
  } else {
    LOG(WARNING) << "Failed to resume task: " << task_id << " not found";
  }
}

void IOTaskController::Cancel(IOTaskId task_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto it = tasks_.find(task_id);
  if (it != tasks_.end()) {
    IOTask* task = it->second.get();
    task->Cancel();
    NotifyIOTaskObservers(task->progress());
    RemoveIOTask(task_id);
  } else {
    LOG(WARNING) << "Failed to cancel task: " << task_id << " not found";
  }
}

void IOTaskController::ProgressPausedTasks() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // TODO(b/255264604): TaskId order is potentially racey when multiple
  // files app windows open. Fix this: develop a concept of the current
  // PAUSED task in this code, and always progress that task.
  for (auto it = tasks_.begin(); it != tasks_.end(); ++it) {
    IOTask* task = it->second.get();
    if (task->progress().IsPaused()) {
      NotifyIOTaskObservers(task->progress());
      break;
    }
  }
}

void IOTaskController::CompleteWithError(IOTaskId task_id,
                                         PolicyError policy_error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto it = tasks_.find(task_id);
  if (it != tasks_.end()) {
    IOTask* task = it->second.get();
    task->CompleteWithError(std::move(policy_error));
    NotifyIOTaskObservers(task->progress());
    RemoveIOTask(task_id);
  } else {
    LOG(WARNING) << "Failed to abort task: " << task_id << " not found";
  }
}

device::mojom::WakeLock* IOTaskController::GetWakeLock() {
  if (!wake_lock_) {
    mojo::Remote<device::mojom::WakeLockProvider> provider;
    content::GetDeviceService().BindWakeLockProvider(
        provider.BindNewPipeAndPassReceiver());
    provider->GetWakeLockWithoutContext(
        device::mojom::WakeLockType::kPreventDisplaySleep,
        device::mojom::WakeLockReason::kOther, "IOTask",
        wake_lock_.BindNewPipeAndPassReceiver());
  }
  return wake_lock_.get();
}

IOTask* IOTaskController::PutIOTask(const IOTaskId task_id,
                                    std::unique_ptr<IOTask> task) {
  // TODO(b/255264604): fix me: PAUSED tasks can hold the wake lock and
  // prevent the device from sleeping.
  if (tasks_.empty()) {
    GetWakeLock()->RequestWakeLock();
    ++wake_lock_counter_for_tests_;
  }

  IOTask* task_ptr = task.get();
  tasks_[task_id] = std::move(task);
  return task_ptr;
}

void IOTaskController::RemoveIOTask(const IOTaskId task_id) {
  tasks_last_update_.erase(task_id);
  tasks_.erase(task_id);

  // TODO(b/255264604): fix me: PAUSED tasks can hold the wake lock and
  // prevent the device from sleeping.
  if (tasks_.empty()) {
    GetWakeLock()->CancelWakeLock();
    --wake_lock_counter_for_tests_;
  }
}

std::vector<std::reference_wrapper<const ProgressStatus>>
IOTaskController::TaskStatuses() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::vector<std::reference_wrapper<const ProgressStatus>> status_vector;
  for (auto& it : tasks_) {
    IOTask* task = it.second.get();
    status_vector.push_back(task->progress());
  }
  return status_vector;
}

}  // namespace io_task

}  // namespace file_manager