chromium/chrome/browser/ash/file_manager/io_task.h

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

#ifndef CHROME_BROWSER_ASH_FILE_MANAGER_IO_TASK_H_
#define CHROME_BROWSER_ASH_FILE_MANAGER_IO_TASK_H_

#include <cstddef>
#include <optional>
#include <ostream>
#include <string>
#include <vector>

#include "base/files/file.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/policy/dlp/dialogs/files_policy_dialog.h"
#include "storage/browser/file_system/file_system_url.h"

class Profile;

namespace file_manager::io_task {

enum class State {
  // Task has been queued, but not yet started.
  kQueued,

  // Task has started, but some initial scanning is performed.
  kScanning,

  // Task is currently running.
  kInProgress,

  // Task is currently paused.
  kPaused,

  // Task has been successfully completed.
  kSuccess,

  // Task has completed with errors.
  kError,

  // Task has failed to finish due to missing password.
  kNeedPassword,

  // Task has been canceled without finishing.
  kCancelled,
};

std::ostream& operator<<(std::ostream& out, State state);

enum class OperationType {
  kCopy,
  kDelete,
  kEmptyTrash,
  kExtract,
  kMove,

  // This restores to the location supplied in the .trashinfo folder, recreating
  // the parent hierarchy as required. As .Trash folders reside on the same
  // filesystem as trashed files, this implies an intra filesystem move.
  kRestore,

  // This restores to a supplied destination only extracting the file name to
  // properly name the destination file. The destination folder is expected to
  // exist and items can be restored cross filesystem.
  kRestoreToDestination,
  kTrash,
  kZip,
};

std::ostream& operator<<(std::ostream& out, OperationType op);

// The type of Data Protection policy error that occurred.
enum class PolicyErrorType {
  // Error caused by Data Leak Prevention policy.
  kDlp,

  // Error caused by Enterprise Connectors policy.
  kEnterpriseConnectors,

  // Error caused by Data Leak Prevention warning timing out.
  kDlpWarningTimeout,
};

// Holds information about data protection policy errors.
struct PolicyError {
  // Type of the error.
  PolicyErrorType type;
  // The number of files blocked by the policy.
  size_t blocked_files = 0;
  // The name of the first file among those under block restriction. Used for
  // notifications.
  std::string file_name;
  // Normally the review button is only shown when `blocked_files` is >1, this
  // option allows to force the display of the review button irrespective of
  // other conditions.
  bool always_show_review = false;

  bool operator==(const PolicyError& other) const;
  bool operator!=(const PolicyError& other) const;
};

// Unique identifier for any type of task.
using IOTaskId = uint64_t;

// I/O task state::PAUSED parameters when paused to resolve a file name
// conflict. Currently, only supported by CopyOrMoveIOTask and
// RestoreToDestinationIOTask.
struct ConflictPauseParams {
  // The conflict file name.
  std::string conflict_name;

  // True if the conflict file name is a directory.
  bool conflict_is_directory = false;

  // Set true if there are potentially multiple conflicted file names.
  bool conflict_multiple = false;

  // The conflict copy or move target URL.
  std::string conflict_target_url;

  bool operator==(const ConflictPauseParams& other) const;
};

// I/O task state::PAUSED parameters when paused to show a policy warning.
// Currently, only supported by CopyOrMovePolicyIOTask and
// RestoreToDestinationIOTask.
struct PolicyPauseParams {
  // One of kDlp, kEnterpriseConnectors.
  policy::Policy type;
  // The number of files under warning restriction. Needed to show correct
  // notifications.
  size_t warning_files_count = 0;
  // The name of the first file among those under warning restriction. Used for
  // notifications.
  std::string file_name;
  // Normally the review button is only shown when `warning_files_count` is >1,
  // this option allows to force the display of the review button irrespective
  // of other conditions.
  bool always_show_review = false;

  bool operator==(const PolicyPauseParams& other) const;
};

// I/O task state::PAUSED parameters. Only one of conflict or policy params
// should be set.
struct PauseParams {
  PauseParams();

  PauseParams(const PauseParams& other);
  PauseParams& operator=(const PauseParams& other);

  PauseParams(PauseParams&& other);
  PauseParams& operator=(PauseParams&& other);

  bool operator==(const PauseParams& other) const;

  ~PauseParams();

  // Set iff pausing due to name conflict.
  std::optional<ConflictPauseParams> conflict_params;
  // Set iff pausing due to a policy warning.
  std::optional<PolicyPauseParams> policy_params;
};

// Resume I/O task parameters when paused because of a name conflict.
struct ConflictResumeParams {
  // How to resolve a CopyOrMoveIOTask file name conflict: either 'keepboth'
  // or 'replace'.
  std::string conflict_resolve;

  // True if |conflict_resolve| should apply to future file name conflicts.
  bool conflict_apply_to_all = false;
};

// Resume I/O task parameters when paused because of a policy.
struct PolicyResumeParams {
  // One of kDlp, kEnterpriseConnectors.
  policy::Policy type;
};

// Resume I/O task parameters.
struct ResumeParams {
  ResumeParams();

  ResumeParams(const ResumeParams& other);
  ResumeParams& operator=(const ResumeParams& other);

  ResumeParams(ResumeParams&& other);
  ResumeParams& operator=(ResumeParams&& other);

  ~ResumeParams();

  // Set iff paused due to name conflict.
  std::optional<ConflictResumeParams> conflict_params;
  // Set iff paused due to a policy warning.
  std::optional<PolicyResumeParams> policy_params;
};

// Represents the status of a particular entry in an I/O task.
struct EntryStatus {
  EntryStatus(storage::FileSystemURL file_url,
              std::optional<base::File::Error> file_error,
              std::optional<storage::FileSystemURL> source_url = std::nullopt);
  ~EntryStatus();

  EntryStatus(EntryStatus&& other);
  EntryStatus& operator=(EntryStatus&& other);

  // The entry FileSystemURL.
  storage::FileSystemURL url;

  // May be empty if the entry has not been fully processed yet.
  std::optional<base::File::Error> error;

  // The source from which the entry identified by `url` is generated. May be
  // empty if not relevant.
  std::optional<storage::FileSystemURL> source_url;

  // True if entry is a directory when its metadata is processed.
  bool is_directory = false;
};

// Represents the current progress of an I/O task.
class ProgressStatus {
 public:
  // Out-of-line constructors to appease the style linter.
  ProgressStatus();
  ProgressStatus(const ProgressStatus& other) = delete;
  ProgressStatus& operator=(const ProgressStatus& other) = delete;
  ~ProgressStatus();

  // Allow ProgressStatus to be moved.
  ProgressStatus(ProgressStatus&& other);
  ProgressStatus& operator=(ProgressStatus&& other);

  // True if the task is in kPaused state.
  bool IsPaused() const;

  // True if the task is in a terminal state and won't receive further updates.
  bool IsCompleted() const;

  // True if the task is paused due to a data protection policy warning.
  bool HasWarning() const;

  // True if the task completed with security errors due to Data Leak Prevention
  // or Enterprise Connectors policies.
  bool HasPolicyError() const;

  // True if the task is in scanning state.
  bool IsScanning() const;

  // Returns a default method for obtaining the source name.
  std::string GetSourceName(Profile* profile) const;

  // Setter for the destination folder and the destination volume id.
  void SetDestinationFolder(storage::FileSystemURL folder,
                            Profile* profile = nullptr);
  const storage::FileSystemURL& GetDestinationFolder() const {
    return destination_folder_;
  }
  const std::string& GetDestinationVolumeId() const {
    return destination_volume_id_;
  }

  // Task state.
  State state;

  // Information about policy errors that occurred, if any. Empty otherwise.
  // Can be set only if Data Leak Prevention or Enterprise Connectors policies
  // apply.
  std::optional<PolicyError> policy_error;

  // I/O Operation type (e.g. copy, move).
  OperationType type;

  // Files the operation processes.
  std::vector<EntryStatus> sources;

  // The file name to use when reporting progress.
  std::string source_name;

  // Entries created by the I/O task. These files aren't necessarily related to
  // |sources|.
  std::vector<EntryStatus> outputs;

  // I/O task state::PAUSED parameters.
  PauseParams pause_params;

  // ProgressStatus over all |sources|.
  int64_t bytes_transferred = 0;

  // Total size of all |sources|.
  int64_t total_bytes = 0;

  // The task id for this progress status.
  IOTaskId task_id = 0;

  // The estimate time to finish the operation.
  double remaining_seconds = 0;

  // Number of `sources` scanned - must be <= `sources.size()`. Only used when
  // in kScanning `state`. When scanning files, the progress is roughly the
  // percentage of the number of scanned items out of the total items. This
  // isn't always accurate, e.g. when uploading entire folders or because some
  // items are not scanned at all. The goal is to show the user that some
  // progress is happening.
  size_t sources_scanned = 0;

  // Whether notifications should be shown on progress status.
  bool show_notification = true;

  // List of files skipped during the operation because we couldn't decrypt
  // them.
  std::vector<storage::FileSystemURL> skipped_encrypted_files;

 private:
  // Optional destination folder for operations that transfer files to a
  // directory (e.g. copy or move).
  storage::FileSystemURL destination_folder_;

  // Volume id of the destination directory for operations that transfer files
  // to a directory (e.g. copy or move).
  std::string destination_volume_id_;
};

// An IOTask represents an I/O operation over multiple files, and is responsible
// for executing the operation and providing progress/completion reports.
class IOTask {
 public:
  IOTask() = delete;
  IOTask(const IOTask& other) = delete;
  IOTask& operator=(const IOTask& other) = delete;
  virtual ~IOTask() = default;

  using ProgressCallback = base::RepeatingCallback<void(const ProgressStatus&)>;
  using CompleteCallback = base::OnceCallback<void(ProgressStatus)>;

  // Executes the task. |progress_callback| should be called every so often to
  // give updates, and |complete_callback| should be only called once at the end
  // to signify completion with a |kSuccess|, |kError| or |kCancelled| state.
  // |progress_callback| should be called on the same sequence Execute() was.
  virtual void Execute(ProgressCallback progress_callback,
                       CompleteCallback complete_callback) = 0;

  // Pauses a task.
  virtual void Pause(PauseParams params);

  // Resumes a task.
  virtual void Resume(ResumeParams params);

  // Cancels the task. This should set the progress state to be |kCancelled|,
  // but not call any of Execute()'s callbacks. The task will be deleted
  // synchronously after this call returns.
  virtual void Cancel() = 0;

  // Aborts the task because of policy error. This should set the progress state
  // to be |kError| with `policy_error` but not call any of Execute()'s
  // callbacks. The task will be deleted synchronously after this call returns.
  virtual void CompleteWithError(PolicyError policy_error);

  // Gets the current progress status of the task.
  const ProgressStatus& progress() { return progress_; }

  // Sets the task id.
  void SetTaskID(IOTaskId task_id) { progress_.task_id = task_id; }

 protected:
  explicit IOTask(bool show_notification) {
    progress_.show_notification = show_notification;
  }

  ProgressStatus progress_;
};

// No-op IO Task for testing.
// TODO(austinct): Move into io_task_controller_unittest file when the other
// IOTasks have been implemented.
class DummyIOTask : public IOTask {
 public:
  DummyIOTask(std::vector<storage::FileSystemURL> source_urls,
              storage::FileSystemURL destination_folder,
              OperationType type,
              bool show_notification = true,
              bool progress_succeeds = true);
  ~DummyIOTask() override;

  // IOTask overrides:
  void Execute(ProgressCallback progress_callback,
               CompleteCallback complete_callback) override;
  void Pause(PauseParams pause_params) override;
  void Resume(ResumeParams resume_params) override;
  void Cancel() override;
  void CompleteWithError(PolicyError policy_error) override;

 private:
  void DoProgress();
  void DoComplete();

  ProgressCallback progress_callback_;
  CompleteCallback complete_callback_;

  // Whether progressing the task should automatically complete it with
  // kSuccess.
  bool progress_succeeds_;

  base::WeakPtrFactory<DummyIOTask> weak_ptr_factory_{this};
};

}  // namespace file_manager::io_task

#endif  // CHROME_BROWSER_ASH_FILE_MANAGER_IO_TASK_H_