| // 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 |