chromium/chrome/browser/ash/policy/skyvault/migration_coordinator.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/migration_coordinator.h"

#include <memory>
#include <optional>

#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notreached.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/drive_skyvault_uploader.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/download/download_dir_util.h"
#include "storage/browser/file_system/file_system_url.h"

namespace policy::local_user_files {

namespace {

// Called after `uploader` is fully stopped.
void OnMigrationStopped(std::unique_ptr<MigrationCloudUploader> uploader) {
  VLOG(1) << "Local files migration stopped";
}

// Returns a path combining `destination_dir` with the file's parent path
// relative to MyFiles.
base::FilePath GetDestinationPath(Profile* profile,
                                  const base::FilePath& file_path,
                                  const std::string& destination_dir) {
  base::FilePath my_files_path = GetMyFilesPath(profile);
  base::FilePath destination_path = base::FilePath(destination_dir);
  my_files_path.AppendRelativePath(file_path.DirName(), &destination_path);
  return destination_path;
}

}  // namespace

MigrationCoordinator::MigrationCoordinator(Profile* profile)
    : profile_(profile) {}

MigrationCoordinator::~MigrationCoordinator() = default;

void MigrationCoordinator::Run(CloudProvider cloud_provider,
                               std::vector<base::FilePath> files,
                               const std::string& destination_dir,
                               MigrationDoneCallback callback) {
  CHECK(!uploader_);

  MigrationDoneCallback wrapped_callback =
      base::BindOnce(&MigrationCoordinator::OnMigrationDone,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback));
  switch (cloud_provider) {
    case CloudProvider::kGoogleDrive:
      uploader_ = std::make_unique<GoogleDriveMigrationUploader>(
          profile_, std::move(files), destination_dir,
          std::move(wrapped_callback));
      break;
    case CloudProvider::kOneDrive:
      uploader_ = std::make_unique<OneDriveMigrationUploader>(
          profile_, std::move(files), destination_dir,
          std::move(wrapped_callback));
      break;
    case CloudProvider::kNotSpecified:
      NOTREACHED_IN_MIGRATION()
          << "Run() should only be called if cloud_provider is specified";
      return;
  }
  uploader_->Run();
}

void MigrationCoordinator::Stop() {
  if (uploader_) {
    MigrationCloudUploader* uploader_ptr = uploader_.get();
    uploader_ptr->Stop(
        base::BindOnce(&OnMigrationStopped, std::move(uploader_)));
  }
}

bool MigrationCoordinator::IsRunning() const {
  return uploader_ != nullptr;
}

void MigrationCoordinator::OnMigrationDone(
    MigrationDoneCallback callback,
    std::map<base::FilePath, MigrationUploadError> errors) {
  uploader_.reset();
  std::move(callback).Run(std::move(errors));
}

MigrationCloudUploader::MigrationCloudUploader(
    Profile* profile,
    std::vector<base::FilePath> files,
    const std::string& destination_dir,
    MigrationDoneCallback callback)
    : profile_(profile),
      files_(std::move(files)),
      destination_dir_(destination_dir),
      callback_(std::move(callback)) {}

MigrationCloudUploader::~MigrationCloudUploader() = default;

OneDriveMigrationUploader::OneDriveMigrationUploader(
    Profile* profile,
    std::vector<base::FilePath> files,
    const std::string& destination_dir,
    MigrationDoneCallback callback)
    : MigrationCloudUploader(profile,
                             std::move(files),
                             destination_dir,
                             std::move(callback)) {}

OneDriveMigrationUploader::~OneDriveMigrationUploader() = default;

void OneDriveMigrationUploader::Run() {
  if (files_.empty()) {
    if (callback_) {
      std::move(callback_).Run({});
    }
    return;
  }
  // TODO(aidazolic): Consider if we can start all jobs at the same time, or we
  // need chunking.
  for (const auto& file_path : files_) {
    // TODO(aidazolic): Ignore files that failed previously.
    base::FilePath target_path =
        GetDestinationPath(profile_, file_path, destination_dir_);
    auto uploader = ash::cloud_upload::OdfsSkyvaultUploader::Upload(
        profile_, file_path,
        ash::cloud_upload::OdfsSkyvaultUploader::FileType::kMigration,
        // No need to show progress updates.
        /*progress_callback=*/base::DoNothing(),
        /*upload_callback=*/
        base::BindOnce(&OneDriveMigrationUploader::OnUploadDone,
                       weak_ptr_factory_.GetWeakPtr(), file_path),
        target_path);
    uploaders_.insert({file_path, uploader});
  }
}

void OneDriveMigrationUploader::Stop(base::OnceClosure callback) {
  // TODO(b/349097807): Stop the uploads.
  std::move(callback).Run();
}

void OneDriveMigrationUploader::SetEmulateSlowForTesting(bool value) {
  CHECK_IS_TEST();
  emulate_slow_for_testing_ = value;
}

void OneDriveMigrationUploader::OnUploadDone(
    const base::FilePath& file_path,
    storage::FileSystemURL url,
    std::optional<MigrationUploadError> error) {
  if (error.has_value()) {
    // TODO(aidazolic): UMA.
    // TODO(aidazolic): Persist the failed file to memory.

    // If we only failed to delete the file, don't fail the entire migration
    // because of it.
    if (error != MigrationUploadError::kDeleteFailed) {
      errors_.insert({file_path, error.value()});
    }
  }

  uploaders_.erase(file_path);
  // If all files are done, invoke the callback.
  if (ShouldFinish() && callback_) {
    std::move(callback_).Run(std::move(errors_));
  }
}

bool OneDriveMigrationUploader::ShouldFinish() {
  if (emulate_slow_for_testing_) {
    CHECK_IS_TEST();
    // Do not run the callback.
    return false;
  }

  return uploaders_.empty();
}

GoogleDriveMigrationUploader::GoogleDriveMigrationUploader(
    Profile* profile,
    std::vector<base::FilePath> files,
    const std::string& destination_dir,
    MigrationDoneCallback callback)
    : MigrationCloudUploader(profile,
                             std::move(files),
                             destination_dir,
                             std::move(callback)) {}

GoogleDriveMigrationUploader::~GoogleDriveMigrationUploader() = default;

void GoogleDriveMigrationUploader::Run() {
  if (files_.empty()) {
    if (callback_) {
      std::move(callback_).Run({});
      return;
    }
  }

  // TODO(aidazolic): Consider if we can start all jobs at the same time, or we
  // need chunking.
  for (const auto& file_path : files_) {
    base::FilePath target_path =
        GetDestinationPath(profile_, file_path, destination_dir_);
    std::unique_ptr<DriveSkyvaultUploader> uploader =
        std::make_unique<DriveSkyvaultUploader>(
            profile_, file_path, target_path,
            base::BindOnce(&GoogleDriveMigrationUploader::OnUploadDone,
                           weak_ptr_factory_.GetWeakPtr(), file_path));

    auto uploader_ptr = uploader.get();
    uploaders_.insert({file_path, std::move(uploader)});
    uploader_ptr->Run();
  }
}

void GoogleDriveMigrationUploader::Stop(base::OnceClosure callback) {
  // TODO(b/349097807): Stop IO tasks.
  std::move(callback).Run();
}

void GoogleDriveMigrationUploader::OnUploadDone(
    const base::FilePath& file_path,
    std::optional<MigrationUploadError> error) {
  if (error.has_value()) {
    // TODO(aidazolic): UMA.
    // TODO(aidazolic): Persist the failed file to memory.

    // If we only failed to delete the file, don't fail the entire migration
    // because of it.
    if (error != MigrationUploadError::kDeleteFailed) {
      errors_.insert({file_path, error.value()});
    }
  }

  uploaders_.erase(file_path);
  // If all files are done, invoke the callback.
  if (uploaders_.empty() && callback_) {
    std::move(callback_).Run(errors_);
  }
}

}  // namespace policy::local_user_files