| // 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/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: | 
 |   CopyOperation( | 
 |       Profile* profile, | 
 |       std::unique_ptr<storage::FileSystemOperationContext> context, | 
 |       const storage::FileSystemURL& src_url, | 
 |       const 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_(src_url), | 
 |         dest_url_(dest_url), | 
 |         options_(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()); | 
 |   } | 
 |  | 
 |   CopyOperation(const CopyOperation&) = delete; | 
 |   CopyOperation& operator=(const CopyOperation&) = delete; | 
 |  | 
 |   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)); | 
 |       origin_task_runner_->DeleteSoon(FROM_HERE, this); | 
 |       return; | 
 |     } | 
 |     drive_integration_service->GetDriveFsInterface()->CopyFile( | 
 |         source_path, destination_path, | 
 |         mojo::WrapCallbackWithDefaultInvokeIfNotRun( | 
 |             base::BindOnce(&CopyOperation::CopyComplete, | 
 |                            base::Unretained(this)), | 
 |             drive::FILE_ERROR_ABORT)); | 
 |   } | 
 |  | 
 |  private: | 
 |   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, | 
 |                            base::Unretained(this))); | 
 |         break; | 
 |  | 
 |       default: | 
 |         origin_task_runner_->PostTask( | 
 |             FROM_HERE, base::BindOnce(std::move(callback_), | 
 |                                       FileErrorToBaseFileError(error))); | 
 |         origin_task_runner_->DeleteSoon(FROM_HERE, this); | 
 |     } | 
 |   } | 
 |  | 
 |   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_)); | 
 |     delete this; | 
 |   } | 
 |  | 
 |   Profile* const 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_; | 
 |   scoped_refptr<base::SequencedTaskRunner> origin_task_runner_; | 
 |   base::WeakPtr<DriveFsAsyncFileUtil> async_file_util_; | 
 | }; | 
 |  | 
 | // Recursively deletes a folder locally. The folder will still be available in | 
 | // Drive cloud Trash. | 
 | class DeleteOperation { | 
 |  public: | 
 |   using PinManager = drivefs::pinning::PinManager; | 
 |   using Id = PinManager::Id; | 
 |  | 
 |   DeleteOperation(Profile* profile, | 
 |                   const base::FilePath& path, | 
 |                   storage::AsyncFileUtil::StatusCallback callback, | 
 |                   scoped_refptr<base::SequencedTaskRunner> origin_task_runner, | 
 |                   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) | 
 |       : profile_(profile), | 
 |         path_(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()); | 
 |   } | 
 |  | 
 |   DeleteOperation(const DeleteOperation&) = delete; | 
 |   DeleteOperation& operator=(const DeleteOperation&) = delete; | 
 |  | 
 |   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)); | 
 |       origin_task_runner_->DeleteSoon(FROM_HERE, this); | 
 |       return; | 
 |     } | 
 |  | 
 |     if (ash::features::IsDriveFsBulkPinningEnabled()) { | 
 |       if (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, | 
 |                                         base::Unretained(this))); | 
 |         return; | 
 |       } | 
 |     } | 
 |  | 
 |     blocking_task_runner_->PostTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce(&DeleteOperation::Delete, base::Unretained(this))); | 
 |   } | 
 |  | 
 |   void OnGotMetadata(const drive::FileError error, | 
 |                      const drivefs::mojom::FileMetadataPtr metadata) { | 
 |     if (error == drive::FILE_ERROR_OK) { | 
 |       DCHECK(metadata); | 
 |       id_ = Id(metadata->stable_id); | 
 |       VLOG(1) << "Got metadata of " << id_ << " '" << drive_path_ << "'"; | 
 |       if (metadata->pinned) { | 
 |         DCHECK(drive_); | 
 |         drive_->GetDriveFsInterface()->SetPinnedByStableId( | 
 |             metadata->stable_id, /*pinned=*/false, | 
 |             base::BindOnce(&DeleteOperation::OnUnpinFile, | 
 |                            base::Unretained(this))); | 
 |         return; | 
 |       } | 
 |     } else { | 
 |       LOG(ERROR) << "Cannot get metadata of '" << drive_path_ << "': " << error; | 
 |     } | 
 |  | 
 |     blocking_task_runner_->PostTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce(&DeleteOperation::Delete, base::Unretained(this))); | 
 |   } | 
 |  | 
 |   void OnUnpinFile(const drive::FileError error) { | 
 |     LOG_IF(ERROR, error != drive::FILE_ERROR_OK) | 
 |         << "Cannot unpin " << id_ << " '" << drive_path_ | 
 |         << "' before deleting it: " << error; | 
 |     blocking_task_runner_->PostTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce(&DeleteOperation::Delete, base::Unretained(this))); | 
 |   } | 
 |  | 
 |   void Delete() { | 
 |     VLOG(1) << "Deleting '" << path_ << "'..."; | 
 |     const bool deleted = base::DeletePathRecursively(path_); | 
 |  | 
 |     if (deleted) { | 
 |       VLOG(1) << "Deleted '" << path_ << "'"; | 
 |       DCHECK(drive_); | 
 |       if (PinManager* const pin_manager = drive_->GetPinManager()) { | 
 |         // TODO(b/267225898): Local delete events are currently not sent via | 
 |         // DriveFS, so for now notify the `PinManager` for local deletes. | 
 |         content::GetUIThreadTaskRunner({})->PostTask( | 
 |             FROM_HERE, | 
 |             base::BindOnce(&PinManager::NotifyDelete, pin_manager->GetWeakPtr(), | 
 |                            id_, drive_path_)); | 
 |       } | 
 |     } 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)); | 
 |     origin_task_runner_->DeleteSoon(FROM_HERE, this); | 
 |   } | 
 |  | 
 |   Profile* const 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::Unretained(new CopyOperation( | 
 |               profile_, std::move(context), src_url, dest_url, 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::Unretained(new DeleteOperation( | 
 |                          profile_, url.path(), std::move(callback), | 
 |                          base::SequencedTaskRunner::GetCurrentDefault(), | 
 |                          context->task_runner())))); | 
 | } | 
 |  | 
 | }  // namespace drive::internal |