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