chromium/chrome/browser/ash/drive/fileapi/drivefs_async_file_util.cc

// Copyright 2018 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/drive/fileapi/drivefs_async_file_util.h"

#include <utility>

#include "ash/constants/ash_features.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "components/drive/file_errors.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_operation.h"
#include "storage/browser/file_system/file_system_operation_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/file_system/local_file_util.h"
#include "storage/browser/file_system/native_file_util.h"
#include "storage/common/file_system/file_system_util.h"

namespace drive::internal {
namespace {

class DriveFsFileUtil : public storage::LocalFileUtil {
 public:
  DriveFsFileUtil() = default;

  DriveFsFileUtil(const DriveFsFileUtil&) = delete;
  DriveFsFileUtil& operator=(const DriveFsFileUtil&) = delete;

  ~DriveFsFileUtil() override = default;

 protected:
  bool IsHiddenItem(const base::FilePath& local_file_path) const override {
    // DriveFS is a trusted filesystem, allow symlinks.
    return false;
  }
};

class CopyOperation : public base::RefCountedThreadSafe<CopyOperation> {
 public:
  CopyOperation(
      Profile* const profile,
      std::unique_ptr<storage::FileSystemOperationContext> context,
      storage::FileSystemURL src_url,
      storage::FileSystemURL dest_url,
      storage::AsyncFileUtil::CopyOrMoveOptionSet options,
      storage::AsyncFileUtil::CopyFileProgressCallback progress_callback,
      storage::AsyncFileUtil::StatusCallback callback,
      scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
      base::WeakPtr<DriveFsAsyncFileUtil> async_file_util)
      : profile_(profile),
        context_(std::move(context)),
        src_url_(std::move(src_url)),
        dest_url_(std::move(dest_url)),
        options_(std::move(options)),
        progress_callback_(std::move(progress_callback)),
        callback_(std::move(callback)),
        origin_task_runner_(std::move(origin_task_runner)),
        async_file_util_(std::move(async_file_util)) {
    DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
  }

  void Start() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    auto* drive_integration_service =
        drive::util::GetIntegrationServiceByProfile(profile_);
    base::FilePath source_path("/");
    base::FilePath destination_path("/");
    if (!drive_integration_service ||
        !drive_integration_service->GetMountPointPath().AppendRelativePath(
            src_url_.path(), &source_path) ||
        !drive_integration_service->GetMountPointPath().AppendRelativePath(
            dest_url_.path(), &destination_path)) {
      origin_task_runner_->PostTask(
          FROM_HERE, base::BindOnce(std::move(callback_),
                                    base::File::FILE_ERROR_INVALID_OPERATION));
      return;
    }
    drive_integration_service->GetDriveFsInterface()->CopyFile(
        source_path, destination_path,
        mojo::WrapCallbackWithDefaultInvokeIfNotRun(
            base::BindOnce(&CopyOperation::CopyComplete, this),
            drive::FILE_ERROR_ABORT));
  }

 private:
  friend class base::RefCountedThreadSafe<CopyOperation>;
  ~CopyOperation() = default;

  void CopyComplete(drive::FileError error) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    switch (error) {
      case drive::FILE_ERROR_NOT_FOUND:
      case drive::FILE_ERROR_NO_CONNECTION:
        origin_task_runner_->PostTask(
            FROM_HERE,
            base::BindOnce(&CopyOperation::FallbackToNativeCopyOnOriginThread,
                           this));
        break;

      default:
        origin_task_runner_->PostTask(
            FROM_HERE, base::BindOnce(std::move(callback_),
                                      FileErrorToBaseFileError(error)));
    }
  }

  void FallbackToNativeCopyOnOriginThread() {
    DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());

    if (!async_file_util_) {
      std::move(callback_).Run(base::File::FILE_ERROR_ABORT);
      return;
    }
    async_file_util_->AsyncFileUtilAdapter::CopyFileLocal(
        std::move(context_), src_url_, dest_url_, options_,
        std::move(progress_callback_), std::move(callback_));
  }

  const raw_ptr<Profile> profile_;
  std::unique_ptr<storage::FileSystemOperationContext> context_;
  const storage::FileSystemURL src_url_;
  const storage::FileSystemURL dest_url_;
  const storage::AsyncFileUtil::CopyOrMoveOptionSet options_;
  storage::AsyncFileUtil::CopyFileProgressCallback progress_callback_;
  storage::AsyncFileUtil::StatusCallback callback_;
  const scoped_refptr<base::SequencedTaskRunner> origin_task_runner_;
  const base::WeakPtr<DriveFsAsyncFileUtil> async_file_util_;
};

// Recursively deletes a folder locally. The folder will still be available in
// Drive cloud Trash.
class DeleteOperation : public base::RefCountedThreadSafe<DeleteOperation> {
 public:
  using PinningManager = drivefs::pinning::PinningManager;
  using Id = PinningManager::Id;

  DeleteOperation(Profile* const profile,
                  base::FilePath path,
                  storage::AsyncFileUtil::StatusCallback callback,
                  scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
                  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
      : profile_(profile),
        path_(std::move(path)),
        callback_(std::move(callback)),
        origin_task_runner_(std::move(origin_task_runner)),
        blocking_task_runner_(std::move(blocking_task_runner)) {
    DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
  }

  void Start() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    DCHECK(!drive_);
    drive_ = drive::util::GetIntegrationServiceByProfile(profile_);
    base::FilePath relative_path;
    if (!drive_ || !drive_->GetMountPointPath().IsParent(path_)) {
      origin_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(callback_), base::File::FILE_ERROR_FAILED));
      return;
    }

    if (drive_->GetPinningManager() &&
        drive_->GetRelativeDrivePath(path_, &drive_path_)) {
      // TODO(b/266168982): In the case this is a folder, only the folder will
      // get unpinned leaving all the children pinned. When the new method is
      // exposed (or parameter on the existing method) update the
      // implementation here.
      drive_->GetDriveFsInterface()->GetMetadata(
          drive_path_, base::BindOnce(&DeleteOperation::OnGotMetadata, this));
      return;
    }

    blocking_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&DeleteOperation::Delete, this));
  }

 private:
  friend class base::RefCountedThreadSafe<DeleteOperation>;
  ~DeleteOperation() = default;

  void OnGotMetadata(const drive::FileError error,
                     const drivefs::mojom::FileMetadataPtr metadata) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    if (error != drive::FILE_ERROR_OK) {
      LOG(ERROR) << "Cannot get metadata of '" << drive_path_ << "': " << error;
    } else {
      DCHECK(metadata);
      id_ = Id(metadata->stable_id);
    }

    blocking_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&DeleteOperation::Delete, this));
  }

  void Delete() {
    VLOG(1) << "Deleting '" << path_ << "'...";
    const bool deleted = base::DeletePathRecursively(path_);

    if (deleted) {
      VLOG(1) << "Deleted '" << path_ << "'";
      content::GetUIThreadTaskRunner({})->PostTask(
          FROM_HERE, base::BindOnce(&DeleteOperation::OnDeleted, this));
    } else {
      LOG(ERROR) << "Cannot delete '" << path_ << "'";
    }

    origin_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback_),
                                  deleted ? base::File::FILE_OK
                                          : base::File::FILE_ERROR_FAILED));
  }

  void OnDeleted() {
    DCHECK(drive_);
    if (PinningManager* const pinning_manager = drive_->GetPinningManager()) {
      // TODO(b/267225898) Local delete events are currently not sent via
      // DriveFS, so for now we notify the `PinningManager` for local deletes.
      pinning_manager->NotifyDelete(id_, drive_path_);
    }
  }

  const raw_ptr<Profile> profile_;
  const base::FilePath path_;
  base::FilePath drive_path_;
  Id id_ = Id::kNone;
  storage::AsyncFileUtil::StatusCallback callback_;
  const scoped_refptr<base::SequencedTaskRunner> origin_task_runner_;
  const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;

  raw_ptr<drive::DriveIntegrationService> drive_ = nullptr;
};

}  // namespace

DriveFsAsyncFileUtil::DriveFsAsyncFileUtil(Profile* profile)
    : AsyncFileUtilAdapter(std::make_unique<DriveFsFileUtil>()),
      profile_(profile) {}

DriveFsAsyncFileUtil::~DriveFsAsyncFileUtil() = default;

void DriveFsAsyncFileUtil::CopyFileLocal(
    std::unique_ptr<storage::FileSystemOperationContext> context,
    const storage::FileSystemURL& src_url,
    const storage::FileSystemURL& dest_url,
    CopyOrMoveOptionSet options,
    CopyFileProgressCallback progress_callback,
    StatusCallback callback) {
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&CopyOperation::Start,
                     base::MakeRefCounted<CopyOperation>(
                         profile_, std::move(context), src_url, dest_url,
                         std::move(options), std::move(progress_callback),
                         std::move(callback),
                         base::SequencedTaskRunner::GetCurrentDefault(),
                         weak_factory_.GetWeakPtr())));
}

void DriveFsAsyncFileUtil::DeleteRecursively(
    std::unique_ptr<storage::FileSystemOperationContext> context,
    const storage::FileSystemURL& url,
    StatusCallback callback) {
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&DeleteOperation::Start,
                     base::MakeRefCounted<DeleteOperation>(
                         profile_, url.path(), std::move(callback),
                         base::SequencedTaskRunner::GetCurrentDefault(),
                         context->task_runner())));
}

}  // namespace drive::internal