chromium/chrome/browser/ash/policy/skyvault/odfs_skyvault_uploader.cc

// Copyright 2024 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/policy/skyvault/odfs_skyvault_uploader.h"

#include <optional>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/file_manager/copy_or_move_io_task.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/io_task_controller.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/policy/skyvault/migration_notification_manager.h"
#include "chrome/browser/ash/policy/skyvault/signin_notification_helper.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
#include "storage/browser/file_system/file_system_url.h"

namespace ash::cloud_upload {

namespace {

// Runs the upload callback provided to `OdfsSkyvaultUploader::Upload`.
void OnUploadDone(
    scoped_refptr<OdfsSkyvaultUploader> odfs_skyvault_uploader,
    base::OnceCallback<void(bool, storage::FileSystemURL)> upload_callback,
    storage::FileSystemURL file_url,
    std::optional<MigrationUploadError> error) {
  std::move(upload_callback).Run(!error.has_value(), std::move(file_url));
}

// Runs the upload callback provided to `OdfsSkyvaultUploader::Upload`.
void OnUploadDoneWithError(
    scoped_refptr<OdfsSkyvaultUploader> odfs_skyvault_uploader,
    base::OnceCallback<void(storage::FileSystemURL,
                            std::optional<MigrationUploadError>)>
        upload_callback,
    storage::FileSystemURL file_url,
    std::optional<MigrationUploadError> error) {
  std::move(upload_callback).Run(std::move(file_url), error);
}

static int64_t g_id_counter = 0;

}  // namespace

// static.
base::WeakPtr<OdfsSkyvaultUploader> OdfsSkyvaultUploader::Upload(
    Profile* profile,
    const base::FilePath& path,
    FileType file_type,
    base::RepeatingCallback<void(int64_t)> progress_callback,
    base::OnceCallback<void(bool, storage::FileSystemURL)> upload_callback) {
  auto* file_system_context =
      file_manager::util::GetFileManagerFileSystemContext(profile);
  DCHECK(file_system_context);
  base::FilePath tmp_dir;
  CHECK((base::GetTempDir(&tmp_dir) && tmp_dir.IsParent(path)) ||
        file_type == FileType::kMigration);
  auto file_system_url = file_system_context->CreateCrackedFileSystemURL(
      blink::StorageKey(), storage::kFileSystemTypeLocal, path);
  scoped_refptr<OdfsSkyvaultUploader> odfs_skyvault_uploader =
      new OdfsSkyvaultUploader(profile, ++g_id_counter, file_system_url,
                               file_type, std::move(progress_callback));

  // Keep `odfs_skyvault_uploader` alive until the upload is done.
  odfs_skyvault_uploader->Run(base::BindOnce(
      &OnUploadDone, odfs_skyvault_uploader, std::move(upload_callback)));
  return odfs_skyvault_uploader->GetWeakPtr();
}

// static.
base::WeakPtr<OdfsSkyvaultUploader> OdfsSkyvaultUploader::Upload(
    Profile* profile,
    const base::FilePath& path,
    FileType file_type,
    base::RepeatingCallback<void(int64_t)> progress_callback,
    UploadDoneCallback upload_callback_with_error,
    const base::FilePath& target_path) {
  auto* file_system_context =
      file_manager::util::GetFileManagerFileSystemContext(profile);
  DCHECK(file_system_context);
  base::FilePath tmp_dir;
  auto file_system_url = file_system_context->CreateCrackedFileSystemURL(
      blink::StorageKey(), storage::kFileSystemTypeLocal, path);

  scoped_refptr<OdfsSkyvaultUploader> odfs_skyvault_uploader;
  switch (file_type) {
    case FileType::kDownload:
    case FileType::kScreenCapture:
      CHECK(base::GetTempDir(&tmp_dir) && tmp_dir.IsParent(path));
      odfs_skyvault_uploader =
          new OdfsSkyvaultUploader(profile, ++g_id_counter, file_system_url,
                                   file_type, std::move(progress_callback));
      break;
    case FileType::kMigration:
      odfs_skyvault_uploader = OdfsMigrationUploader::Create(
          profile, ++g_id_counter, file_system_url, target_path);
      break;
  }

  // Keep `odfs_skyvault_uploader` alive until the upload is done.
  odfs_skyvault_uploader->Run(
      base::BindOnce(&OnUploadDoneWithError, odfs_skyvault_uploader,
                     std::move(upload_callback_with_error)));
  return odfs_skyvault_uploader->GetWeakPtr();
}

base::WeakPtr<OdfsSkyvaultUploader> OdfsSkyvaultUploader::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void OdfsSkyvaultUploader::Cancel() {
  cancelled_ = true;
  if (observed_task_id_.has_value()) {
    io_task_controller_->Cancel(observed_task_id_.value());
  }
}

OdfsSkyvaultUploader::OdfsSkyvaultUploader(
    Profile* profile,
    int64_t id,
    const storage::FileSystemURL& file_system_url,
    FileType file_type,
    base::RepeatingCallback<void(int64_t)> progress_callback)
    : profile_(profile),
      file_system_context_(
          file_manager::util::GetFileManagerFileSystemContext(profile)),
      id_(id),
      file_system_url_(file_system_url),
      file_type_(file_type),
      progress_callback_(std::move(progress_callback)) {}

OdfsSkyvaultUploader::~OdfsSkyvaultUploader() {
  // Stop observing IO task updates.
  if (io_task_controller_) {
    io_task_controller_->RemoveObserver(this);
  }
}

base::FilePath OdfsSkyvaultUploader::GetDestinationFolderPath(
    file_system_provider::ProvidedFileSystemInterface* file_system) {
  return file_system->GetFileSystemInfo().mount_path();
}

void OdfsSkyvaultUploader::RequestSignIn(
    base::OnceCallback<void(base::File::Error)> on_sign_in_cb) {
  policy::skyvault_ui_utils::ShowSignInNotification(
      profile_, id_, file_type_, file_system_url_.path().BaseName().value(),
      std::move(on_sign_in_cb));
}

void OdfsSkyvaultUploader::Run(UploadDoneCallback upload_callback) {
  upload_callback_ = std::move(upload_callback);

  if (cancelled_) {
    OnEndUpload(/*url=*/{}, MigrationUploadError::kCancelled);
    return;
  }

  if (!profile_) {
    LOG(ERROR) << "No profile";
    OnEndUpload(/*url=*/{}, MigrationUploadError::kOther);
    return;
  }

  file_manager::VolumeManager* volume_manager =
      (file_manager::VolumeManager::Get(profile_));
  if (!volume_manager) {
    LOG(ERROR) << "No volume manager";
    OnEndUpload(/*url=*/{}, MigrationUploadError::kOther);
    return;
  }
  io_task_controller_ = volume_manager->io_task_controller();
  if (!io_task_controller_) {
    LOG(ERROR) << "No task_controller";
    OnEndUpload(/*url=*/{}, MigrationUploadError::kOther);
    return;
  }

  // Observe IO tasks updates.
  io_task_controller_->AddObserver(this);

  GetODFSMetadataAndStartIOTask();
}

void OdfsSkyvaultUploader::OnEndUpload(
    storage::FileSystemURL url,
    std::optional<MigrationUploadError> error) {
  if (upload_callback_) {
    std::move(upload_callback_).Run(std::move(url), error);
  }
}

void OdfsSkyvaultUploader::GetODFSMetadataAndStartIOTask() {
  file_system_provider::ProvidedFileSystemInterface* file_system =
      GetODFS(profile_);
  if (!file_system) {
    RequestSignIn(base::BindOnce(&OdfsSkyvaultUploader::OnMountResponse,
                                 weak_ptr_factory_.GetWeakPtr()));
    return;
  }
  // First check that ODFS is not in the "ReauthenticationRequired" state.
  GetODFSMetadata(
      file_system,
      base::BindOnce(&OdfsSkyvaultUploader::CheckReauthenticationAndStartIOTask,
                     weak_ptr_factory_.GetWeakPtr()));
}

void OdfsSkyvaultUploader::CheckReauthenticationAndStartIOTask(
    base::expected<ODFSMetadata, base::File::Error> metadata_or_error) {
  if (!metadata_or_error.has_value()) {
    // Try the move anyway.
    LOG(ERROR) << "Failed to get reauthentication required state: "
               << metadata_or_error.error();
  } else if (metadata_or_error->reauthentication_required ||
             (metadata_or_error->account_state.has_value() &&
              metadata_or_error->account_state.value() ==
                  OdfsAccountState::kReauthenticationRequired)) {
    // TODO(b/330786891): Only query account_state once
    // reauthentication_required is no longer needed for backwards compatibility
    // with ODFS.
    RequestSignIn(base::BindOnce(&OdfsSkyvaultUploader::OnMountResponse,
                                 weak_ptr_factory_.GetWeakPtr()));
    return;
  }
  StartIOTask();
}

void OdfsSkyvaultUploader::OnIOTaskStatus(
    const file_manager::io_task::ProgressStatus& status) {
  if (status.task_id != observed_task_id_) {
    return;
  }
  switch (status.state) {
    case file_manager::io_task::State::kInProgress:
      if (status.bytes_transferred > 0) {
        progress_callback_.Run(status.bytes_transferred);
      }
      return;
    case file_manager::io_task::State::kPaused:
    case file_manager::io_task::State::kScanning:
    case file_manager::io_task::State::kQueued:
      return;
    case file_manager::io_task::State::kSuccess:
      progress_callback_.Run(status.bytes_transferred);
      OnEndUpload(status.outputs[0].url);
      return;
    case file_manager::io_task::State::kCancelled:
    case file_manager::io_task::State::kError:
      OnEndUpload(/*url=*/{}, MigrationUploadError::kCopyFailed);
      return;
    case file_manager::io_task::State::kNeedPassword:
      NOTREACHED_IN_MIGRATION()
          << "Encrypted file should not need password to be copied or "
             "moved. Case should not be reached.";
      return;
  }
}

void OdfsSkyvaultUploader::OnMountResponse(base::File::Error result) {
  if (cancelled_) {
    OnEndUpload(/*url=*/{}, MigrationUploadError::kCancelled);
    return;
  }

  if (result != base::File::Error::FILE_OK) {
    LOG(ERROR) << "Failed to mount ODFS: " << result;
    OnEndUpload(/*url=*/{}, MigrationUploadError::kServiceUnavailable);
    return;
  }

  StartIOTask();
}

void OdfsSkyvaultUploader::StartIOTask() {
  if (observed_task_id_.has_value()) {
    NOTREACHED_IN_MIGRATION()
        << "The IOTask was already triggered. Case should not be "
           "reached.";
  }

  if (cancelled_) {
    OnEndUpload(/*url=*/{}, MigrationUploadError::kCancelled);
    return;
  }

  file_system_provider::ProvidedFileSystemInterface* file_system =
      GetODFS(profile_);
  if (!file_system) {
    // If the file system doesn't exist at this point, then just fail.
    OnEndUpload(/*url=*/{}, MigrationUploadError::kServiceUnavailable);
    return;
  }

  auto destination_folder_path = GetDestinationFolderPath(file_system);

  auto destination_folder_url = FilePathToFileSystemURL(
      profile_, file_system_context_, destination_folder_path);
  if (!destination_folder_url.is_valid()) {
    LOG(ERROR) << "Unable to generate destination folder ODFS URL";
    OnEndUpload(/*url=*/{}, MigrationUploadError::kCopyFailed);
    return;
  }

  std::unique_ptr<file_manager::io_task::IOTask> task =
      std::make_unique<file_manager::io_task::CopyOrMoveIOTask>(
          file_manager::io_task::OperationType::kMove,
          std::vector<storage::FileSystemURL>{file_system_url_},
          std::move(destination_folder_url), profile_, file_system_context_,
          /*show_notification=*/false);

  observed_task_id_ = io_task_controller_->Add(std::move(task));
}

// =========
// MIGRATION
// =========

// static
scoped_refptr<OdfsMigrationUploader> OdfsMigrationUploader::Create(
    Profile* profile,
    int64_t id,
    const storage::FileSystemURL& file_system_url,
    const base::FilePath& target_path) {
  return new OdfsMigrationUploader(profile, id, file_system_url, target_path);
}

OdfsMigrationUploader::OdfsMigrationUploader(
    Profile* profile,
    int64_t id,
    const storage::FileSystemURL& file_system_url,
    const base::FilePath& target_path)
    : OdfsSkyvaultUploader(profile,
                           id,
                           file_system_url,
                           FileType::kMigration,
                           /*progress_callback=*/base::DoNothing()),
      target_path_(target_path) {}

OdfsMigrationUploader::~OdfsMigrationUploader() = default;

base::FilePath OdfsMigrationUploader::GetDestinationFolderPath(
    file_system_provider::ProvidedFileSystemInterface* file_system) {
  base::FilePath path =
      OdfsSkyvaultUploader::GetDestinationFolderPath(file_system)
          .Append(target_path_);
  return path;
}

void OdfsMigrationUploader::RequestSignIn(
    base::OnceCallback<void(base::File::Error)> on_sign_in_cb) {
  policy::local_user_files::MigrationNotificationManager* notification_manager =
      policy::local_user_files::MigrationNotificationManagerFactory::
          GetForBrowserContext(profile_);
  CHECK(notification_manager);
  subscription_ = notification_manager->ShowOneDriveSignInNotification(
      std::move(on_sign_in_cb));
}

}  // namespace ash::cloud_upload