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

#include "chrome/browser/ash/file_manager/delete_io_task.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.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 {

const int kNoSyncUpdatesTimeOutMs = 60000;  // 1 Minute.

file_manager::io_task::IOTaskController* GetIOTaskController(Profile* profile) {
  DCHECK(profile);

  file_manager::VolumeManager* volume_manager =
      file_manager::VolumeManager::Get(profile);
  if (!volume_manager) {
    LOG(ERROR) << "No Volume Manager";
    return nullptr;
  }

  return volume_manager->io_task_controller();
}

// Runs the upload callback provided to `DriveUploadObserver::Observe`.
void OnUploadDone(scoped_refptr<DriveUploadObserver> drive_upload_observer,
                  base::OnceCallback<void(bool)> upload_callback,
                  bool success) {
  std::move(upload_callback).Run(success);
}

}  // namespace

// static.
void DriveUploadObserver::Observe(
    Profile* profile,
    base::FilePath file_path,
    int64_t file_bytes,
    base::RepeatingCallback<void(int64_t)> progress_callback,
    base::OnceCallback<void(bool)> upload_callback) {
  scoped_refptr<DriveUploadObserver> drive_upload_observer =
      new DriveUploadObserver(profile, file_path, file_bytes,
                              std::move(progress_callback));

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

DriveUploadObserver::DriveUploadObserver(
    Profile* profile,
    base::FilePath file_path,
    int64_t file_bytes,
    base::RepeatingCallback<void(int64_t)> progress_callback)
    : profile_(profile),
      file_system_context_(
          file_manager::util::GetFileManagerFileSystemContext(profile)),
      drive_integration_service_(
          drive::DriveIntegrationServiceFactory::FindForProfile(profile)),
      observed_local_path_(file_path),
      file_bytes_(file_bytes),
      progress_callback_(std::move(progress_callback)) {}

DriveUploadObserver::~DriveUploadObserver() = default;

void DriveUploadObserver::Run(base::OnceCallback<void(bool)> upload_callback) {
  upload_callback_ = std::move(upload_callback);
  if (!profile_) {
    LOG(ERROR) << "No profile";
    OnEndUpload(/*success=*/false);
    return;
  }

  if (drive::util::GetDriveConnectionStatus(profile_) !=
      drive::util::ConnectionStatus::kConnected) {
    LOG(ERROR) << "No connection to Drive";
    OnEndUpload(/*success=*/false);
    return;
  }

  if (!drive_integration_service_) {
    LOG(ERROR) << "No Drive integration service";
    OnEndUpload(/*success=*/false);
    return;
  }

  // Observe Drive updates.
  drive::DriveIntegrationService::Observer::Observe(drive_integration_service_);
  drivefs::DriveFsHost::Observer::Observe(
      drive_integration_service_->GetDriveFsHost());

  if (!drive_integration_service_->IsMounted()) {
    LOG(ERROR) << "Google Drive is not mounted";
    OnEndUpload(/*success=*/false);
    return;
  }

  if (observed_local_path_.empty() ||
      !drive_integration_service_->GetRelativeDrivePath(
          observed_local_path_, &observed_drive_path_)) {
    OnEndUpload(/*success=*/false);
    return;
  }

  StartNoSyncUpdateTimer();
}

void DriveUploadObserver::OnEndUpload(bool success) {
  if (no_sync_update_timeout_.IsRunning()) {
    no_sync_update_timeout_.Reset();
  }

  // If the file sync to to Drive was unsuccessful, delete the file from the
  // Local cache.
  if (!success) {
    if (!observed_delete_task_id_.has_value()) {
      auto* io_task_controller = GetIOTaskController(profile_);

      DCHECK(io_task_controller);
      DCHECK(!io_task_controller_observer_.IsObserving());

      io_task_controller_observer_.Observe(io_task_controller);
      storage::FileSystemURL file_url = FilePathToFileSystemURL(
          profile_, file_system_context_, observed_local_path_);
      std::unique_ptr<file_manager::io_task::IOTask> task =
          std::make_unique<file_manager::io_task::DeleteIOTask>(
              std::vector<storage::FileSystemURL>{file_url},
              file_system_context_,
              /*show_notification=*/false);
      observed_delete_task_id_ = io_task_controller->Add(std::move(task));
    }
  } else {
    std::move(upload_callback_).Run(success);
  }
}

void DriveUploadObserver::OnUnmounted() {
  OnEndUpload(/*success=*/false);
}

void DriveUploadObserver::OnSyncingStatusUpdate(
    const drivefs::mojom::SyncingStatus& syncing_status) {
  for (const auto& item : syncing_status.item_events) {
    if (base::FilePath(item->path) != observed_drive_path_) {
      continue;
    }

    // Restart the timer.
    StartNoSyncUpdateTimer();

    switch (item->state) {
      case drivefs::mojom::ItemEvent::State::kQueued: {
        // Tell Drive to upload the file now. If successful, we will receive a
        // kInProgress or kCompleted event sooner. If this fails, we ignore it.
        // The file will get uploaded eventually.
        drive_integration_service_->ImmediatelyUpload(
            observed_drive_path_,
            base::BindOnce(&DriveUploadObserver::OnImmediatelyUploadDone,
                           weak_ptr_factory_.GetWeakPtr(),
                           item->bytes_to_transfer));
        return;
      }
      case drivefs::mojom::ItemEvent::State::kInProgress:
        if (item->bytes_transferred > 0) {
          progress_callback_.Run(item->bytes_transferred);
        }
        return;
      case drivefs::mojom::ItemEvent::State::kCompleted:
        progress_callback_.Run(item->bytes_transferred);
        OnEndUpload(/*success=*/true);
        return;
      case drivefs::mojom::ItemEvent::State::kFailed:
        LOG(ERROR) << "Drive sync error: failed";
        OnEndUpload(/*success=*/false);
        return;
      case drivefs::mojom::ItemEvent::State::kCancelledAndDeleted:
      case drivefs::mojom::ItemEvent::State::kCancelledAndTrashed:
        LOG(ERROR) << "Drive sync error: cancelled and deleted/trashed";
        OnEndUpload(/*success=*/false);
        return;
    }
  }
}

void DriveUploadObserver::OnError(const drivefs::mojom::DriveError& error) {
  if (base::FilePath(error.path) != observed_drive_path_) {
    return;
  }

  OnEndUpload(/*success=*/false);
}

void DriveUploadObserver::OnDriveConnectionStatusChanged(
    drive::util::ConnectionStatus status) {
  if (status != drive::util::ConnectionStatus::kConnected) {
    LOG(ERROR) << "Lost connection to Drive during upload";
    OnEndUpload(/*success=*/false);
  }
}

void DriveUploadObserver::OnIOTaskStatus(
    const ::file_manager::io_task::ProgressStatus& status) {
  if (status.task_id != observed_delete_task_id_) {
    return;
  }

  switch (status.state) {
    case file_manager::io_task::State::kCancelled:
      NOTREACHED_IN_MIGRATION()
          << "Deletion of source or destination file should not have "
             "been cancelled.";
      ABSL_FALLTHROUGH_INTENDED;
    case file_manager::io_task::State::kError:
      LOG(ERROR) << "Deleting the file from the local cache failed.";
      ABSL_FALLTHROUGH_INTENDED;
    case file_manager::io_task::State::kSuccess:
      std::move(upload_callback_).Run(false);
      return;
    default:
      return;
  }
}

void DriveUploadObserver::OnImmediatelyUploadDone(int64_t bytes_transferred,
                                                  drive::FileError error) {
  LOG_IF(ERROR, error != drive::FileError::FILE_ERROR_OK)
      << "ImmediatelyUpload failed with status: " << error;
  if (error != drive::FileError::FILE_ERROR_OK) {
    OnEndUpload(/*success=*/false);
  } else {
    if (bytes_transferred == file_bytes_) {
      // The file is successfully uploaded.
      OnEndUpload(/*success=*/true);
    } else if (bytes_transferred < file_bytes_) {
      // This the first event for just creating the file.
      progress_callback_.Run(bytes_transferred);
    }
  }
}

void DriveUploadObserver::StartNoSyncUpdateTimer() {
  no_sync_update_timeout_.Start(
      FROM_HERE, base::Milliseconds(kNoSyncUpdatesTimeOutMs),
      base::BindOnce(&DriveUploadObserver::NoSyncTimedOut,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DriveUploadObserver::NoSyncTimedOut() {
  drive_integration_service_->GetDriveFsInterface()->GetMetadata(
      observed_drive_path_,
      base::BindOnce(&DriveUploadObserver::OnGetDriveMetadata,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DriveUploadObserver::OnGetDriveMetadata(
    drive::FileError error,
    drivefs::mojom::FileMetadataPtr metadata) {
  if (error != drive::FileError::FILE_ERROR_OK || !metadata) {
    OnEndUpload(/*success=*/false);
    return;
  }

  GURL download_url(metadata->download_url);
  if (download_url.is_valid()) {
    // The file was already uploaded.
    OnEndUpload(/*success=*/true);
    return;
  }

  OnEndUpload(/*success=*/false);
}

}  // namespace ash::cloud_upload