| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "webkit/browser/fileapi/copy_or_move_operation_delegate.h" |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "webkit/browser/fileapi/copy_or_move_file_validator.h" |
| #include "webkit/browser/fileapi/file_system_context.h" |
| #include "webkit/browser/fileapi/file_system_operation_runner.h" |
| #include "webkit/browser/fileapi/file_system_url.h" |
| #include "webkit/browser/fileapi/recursive_operation_delegate.h" |
| #include "webkit/common/blob/shareable_file_reference.h" |
| #include "webkit/common/fileapi/file_system_util.h" |
| |
| namespace fileapi { |
| |
| class CopyOrMoveOperationDelegate::CopyOrMoveImpl { |
| public: |
| virtual ~CopyOrMoveImpl() {} |
| virtual void Run( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback) = 0; |
| protected: |
| CopyOrMoveImpl() {} |
| DISALLOW_COPY_AND_ASSIGN(CopyOrMoveImpl); |
| }; |
| |
| namespace { |
| |
| // Copies a file on a (same) file system. Just delegate the operation to |
| // |operation_runner|. |
| class CopyOrMoveOnSameFileSystemImpl |
| : public CopyOrMoveOperationDelegate::CopyOrMoveImpl { |
| public: |
| CopyOrMoveOnSameFileSystemImpl( |
| FileSystemOperationRunner* operation_runner, |
| CopyOrMoveOperationDelegate::OperationType operation_type, |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| const FileSystemOperation::CopyFileProgressCallback& |
| file_progress_callback) |
| : operation_runner_(operation_runner), |
| operation_type_(operation_type), |
| src_url_(src_url), |
| dest_url_(dest_url), |
| file_progress_callback_(file_progress_callback) { |
| } |
| |
| virtual void Run( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE { |
| if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_MOVE) { |
| operation_runner_->MoveFileLocal(src_url_, dest_url_, callback); |
| } else { |
| operation_runner_->CopyFileLocal( |
| src_url_, dest_url_, file_progress_callback_, callback); |
| } |
| } |
| |
| private: |
| FileSystemOperationRunner* operation_runner_; |
| CopyOrMoveOperationDelegate::OperationType operation_type_; |
| FileSystemURL src_url_; |
| FileSystemURL dest_url_; |
| FileSystemOperation::CopyFileProgressCallback file_progress_callback_; |
| DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOnSameFileSystemImpl); |
| }; |
| |
| // Specifically for cross file system copy/move operation, this class creates |
| // a snapshot file, validates it if necessary, runs copying process, |
| // validates the created file, and removes source file for move (noop for |
| // copy). |
| class SnapshotCopyOrMoveImpl |
| : public CopyOrMoveOperationDelegate::CopyOrMoveImpl { |
| public: |
| SnapshotCopyOrMoveImpl( |
| FileSystemOperationRunner* operation_runner, |
| CopyOrMoveOperationDelegate::OperationType operation_type, |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| CopyOrMoveFileValidatorFactory* validator_factory, |
| const FileSystemOperation::CopyFileProgressCallback& |
| file_progress_callback) |
| : operation_runner_(operation_runner), |
| operation_type_(operation_type), |
| src_url_(src_url), |
| dest_url_(dest_url), |
| validator_factory_(validator_factory), |
| file_progress_callback_(file_progress_callback), |
| weak_factory_(this) { |
| } |
| |
| virtual void Run( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE { |
| file_progress_callback_.Run(0); |
| operation_runner_->CreateSnapshotFile( |
| src_url_, |
| base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCreateSnapshot, |
| weak_factory_.GetWeakPtr(), callback)); |
| } |
| |
| private: |
| void RunAfterCreateSnapshot( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error, |
| const base::PlatformFileInfo& file_info, |
| const base::FilePath& platform_path, |
| const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { |
| if (error != base::PLATFORM_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| // For now we assume CreateSnapshotFile always return a valid local file |
| // path. |
| DCHECK(!platform_path.empty()); |
| |
| if (!validator_factory_) { |
| // No validation is needed. |
| RunAfterPreWriteValidation(platform_path, file_info, file_ref, callback, |
| base::PLATFORM_FILE_OK); |
| return; |
| } |
| |
| // Run pre write validation. |
| PreWriteValidation( |
| platform_path, |
| base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPreWriteValidation, |
| weak_factory_.GetWeakPtr(), |
| platform_path, file_info, file_ref, callback)); |
| } |
| |
| void RunAfterPreWriteValidation( |
| const base::FilePath& platform_path, |
| const base::PlatformFileInfo& file_info, |
| const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref, |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error != base::PLATFORM_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| // |file_ref| is unused but necessary to keep the file alive until |
| // CopyInForeignFile() is completed. |
| operation_runner_->CopyInForeignFile( |
| platform_path, dest_url_, |
| base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCopyInForeignFile, |
| weak_factory_.GetWeakPtr(), file_info, file_ref, callback)); |
| } |
| |
| void RunAfterCopyInForeignFile( |
| const base::PlatformFileInfo& file_info, |
| const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref, |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error != base::PLATFORM_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| file_progress_callback_.Run(file_info.size); |
| |
| // |validator_| is NULL when the destination filesystem does not do |
| // validation. |
| if (!validator_) { |
| // No validation is needed. |
| RunAfterPostWriteValidation(callback, base::PLATFORM_FILE_OK); |
| return; |
| } |
| |
| PostWriteValidation( |
| base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPostWriteValidation, |
| weak_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void RunAfterPostWriteValidation( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error != base::PLATFORM_FILE_OK) { |
| // Failed to validate. Remove the destination file. |
| operation_runner_->Remove( |
| dest_url_, true /* recursive */, |
| base::Bind(&SnapshotCopyOrMoveImpl::DidRemoveDestForError, |
| weak_factory_.GetWeakPtr(), error, callback)); |
| return; |
| } |
| |
| if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_COPY) { |
| callback.Run(base::PLATFORM_FILE_OK); |
| return; |
| } |
| |
| DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE, operation_type_); |
| |
| // Remove the source for finalizing move operation. |
| operation_runner_->Remove( |
| src_url_, true /* recursive */, |
| base::Bind(&SnapshotCopyOrMoveImpl::RunAfterRemoveSourceForMove, |
| weak_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void RunAfterRemoveSourceForMove( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) |
| error = base::PLATFORM_FILE_OK; |
| callback.Run(error); |
| } |
| |
| void DidRemoveDestForError( |
| base::PlatformFileError prior_error, |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error != base::PLATFORM_FILE_OK) { |
| VLOG(1) << "Error removing destination file after validation error: " |
| << error; |
| } |
| callback.Run(prior_error); |
| } |
| |
| // Runs pre-write validation. |
| void PreWriteValidation( |
| const base::FilePath& platform_path, |
| const CopyOrMoveOperationDelegate::StatusCallback& callback) { |
| DCHECK(validator_factory_); |
| validator_.reset( |
| validator_factory_->CreateCopyOrMoveFileValidator( |
| src_url_, platform_path)); |
| validator_->StartPreWriteValidation(callback); |
| } |
| |
| // Runs post-write validation. |
| void PostWriteValidation( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback) { |
| operation_runner_->CreateSnapshotFile( |
| dest_url_, |
| base::Bind( |
| &SnapshotCopyOrMoveImpl::PostWriteValidationAfterCreateSnapshotFile, |
| weak_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void PostWriteValidationAfterCreateSnapshotFile( |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error, |
| const base::PlatformFileInfo& file_info, |
| const base::FilePath& platform_path, |
| const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { |
| if (error != base::PLATFORM_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| DCHECK(validator_); |
| // Note: file_ref passed here to keep the file alive until after |
| // the StartPostWriteValidation operation finishes. |
| validator_->StartPostWriteValidation( |
| platform_path, |
| base::Bind(&SnapshotCopyOrMoveImpl::DidPostWriteValidation, |
| weak_factory_.GetWeakPtr(), file_ref, callback)); |
| } |
| |
| // |file_ref| is unused; it is passed here to make sure the reference is |
| // alive until after post-write validation is complete. |
| void DidPostWriteValidation( |
| const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref, |
| const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| base::PlatformFileError error) { |
| callback.Run(error); |
| } |
| |
| FileSystemOperationRunner* operation_runner_; |
| CopyOrMoveOperationDelegate::OperationType operation_type_; |
| FileSystemURL src_url_; |
| FileSystemURL dest_url_; |
| CopyOrMoveFileValidatorFactory* validator_factory_; |
| scoped_ptr<CopyOrMoveFileValidator> validator_; |
| FileSystemOperation::CopyFileProgressCallback file_progress_callback_; |
| |
| base::WeakPtrFactory<SnapshotCopyOrMoveImpl> weak_factory_; |
| DISALLOW_COPY_AND_ASSIGN(SnapshotCopyOrMoveImpl); |
| }; |
| |
| } // namespace |
| |
| |
| CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate( |
| FileSystemContext* file_system_context, |
| const FileSystemURL& src_root, |
| const FileSystemURL& dest_root, |
| OperationType operation_type, |
| const CopyProgressCallback& progress_callback, |
| const StatusCallback& callback) |
| : RecursiveOperationDelegate(file_system_context), |
| src_root_(src_root), |
| dest_root_(dest_root), |
| operation_type_(operation_type), |
| progress_callback_(progress_callback), |
| callback_(callback), |
| weak_factory_(this) { |
| same_file_system_ = src_root_.IsInSameFileSystem(dest_root_); |
| } |
| |
| CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() { |
| STLDeleteElements(&running_copy_set_); |
| } |
| |
| void CopyOrMoveOperationDelegate::Run() { |
| // Not supported; this should never be called. |
| NOTREACHED(); |
| } |
| |
| void CopyOrMoveOperationDelegate::RunRecursively() { |
| // Perform light-weight checks first. |
| |
| // It is an error to try to copy/move an entry into its child. |
| if (same_file_system_ && src_root_.path().IsParent(dest_root_.path())) { |
| callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| |
| // It is an error to copy/move an entry into the same path. |
| if (same_file_system_ && src_root_.path() == dest_root_.path()) { |
| callback_.Run(base::PLATFORM_FILE_ERROR_EXISTS); |
| return; |
| } |
| |
| // Start to process the source directory recursively. |
| // TODO(kinuko): This could be too expensive for same_file_system_==true |
| // and operation==MOVE case, probably we can just rename the root directory. |
| // http://crbug.com/172187 |
| StartRecursiveOperation(src_root_, callback_); |
| } |
| |
| void CopyOrMoveOperationDelegate::ProcessFile( |
| const FileSystemURL& src_url, |
| const StatusCallback& callback) { |
| if (!progress_callback_.is_null()) |
| progress_callback_.Run(FileSystemOperation::BEGIN_COPY_ENTRY, src_url, 0); |
| |
| FileSystemURL dest_url = CreateDestURL(src_url); |
| CopyOrMoveImpl* impl = NULL; |
| if (same_file_system_) { |
| impl = new CopyOrMoveOnSameFileSystemImpl( |
| operation_runner(), operation_type_, src_url, dest_url, |
| base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, |
| weak_factory_.GetWeakPtr(), src_url)); |
| } else { |
| // Cross filesystem case. |
| // TODO(hidehiko): Support stream based copy. crbug.com/279287. |
| base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; |
| CopyOrMoveFileValidatorFactory* validator_factory = |
| file_system_context()->GetCopyOrMoveFileValidatorFactory( |
| dest_root_.type(), &error); |
| if (error != base::PLATFORM_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| impl = new SnapshotCopyOrMoveImpl( |
| operation_runner(), operation_type_, src_url, dest_url, |
| validator_factory, |
| base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, |
| weak_factory_.GetWeakPtr(), src_url)); |
| } |
| |
| // Register the running task. |
| running_copy_set_.insert(impl); |
| impl->Run(base::Bind(&CopyOrMoveOperationDelegate::DidCopyOrMoveFile, |
| weak_factory_.GetWeakPtr(), src_url, callback, impl)); |
| } |
| |
| void CopyOrMoveOperationDelegate::ProcessDirectory( |
| const FileSystemURL& src_url, |
| const StatusCallback& callback) { |
| if (src_url == src_root_) { |
| // The src_root_ looks to be a directory. |
| // Try removing the dest_root_ to see if it exists and/or it is an |
| // empty directory. |
| // We do not invoke |progress_callback_| for source root, because it is |
| // already called in ProcessFile(). |
| operation_runner()->RemoveDirectory( |
| dest_root_, |
| base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot, |
| weak_factory_.GetWeakPtr(), callback)); |
| return; |
| } |
| |
| if (!progress_callback_.is_null()) |
| progress_callback_.Run(FileSystemOperation::BEGIN_COPY_ENTRY, src_url, 0); |
| |
| ProcessDirectoryInternal(src_url, CreateDestURL(src_url), callback); |
| } |
| |
| void CopyOrMoveOperationDelegate::PostProcessDirectory( |
| const FileSystemURL& src_url, |
| const StatusCallback& callback) { |
| if (operation_type_ == OPERATION_COPY) { |
| callback.Run(base::PLATFORM_FILE_OK); |
| return; |
| } |
| |
| DCHECK_EQ(OPERATION_MOVE, operation_type_); |
| |
| // All files and subdirectories in the directory should be moved here, |
| // so remove the source directory for finalizing move operation. |
| operation_runner()->Remove( |
| src_url, false /* recursive */, |
| base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove, |
| weak_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void CopyOrMoveOperationDelegate::DidCopyOrMoveFile( |
| const FileSystemURL& src_url, |
| const StatusCallback& callback, |
| CopyOrMoveImpl* impl, |
| base::PlatformFileError error) { |
| running_copy_set_.erase(impl); |
| delete impl; |
| |
| if (!progress_callback_.is_null() && error == base::PLATFORM_FILE_OK) |
| progress_callback_.Run(FileSystemOperation::END_COPY_ENTRY, src_url, 0); |
| |
| callback.Run(error); |
| } |
| |
| void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot( |
| const StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) { |
| callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| if (error != base::PLATFORM_FILE_OK && |
| error != base::PLATFORM_FILE_ERROR_NOT_FOUND) { |
| callback_.Run(error); |
| return; |
| } |
| |
| ProcessDirectoryInternal(src_root_, dest_root_, callback); |
| } |
| |
| void CopyOrMoveOperationDelegate::ProcessDirectoryInternal( |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| const StatusCallback& callback) { |
| // If operation_type == Move we may need to record directories and |
| // restore directory timestamps in the end, though it may have |
| // negative performance impact. |
| // See http://crbug.com/171284 for more details. |
| operation_runner()->CreateDirectory( |
| dest_url, false /* exclusive */, false /* recursive */, |
| base::Bind(&CopyOrMoveOperationDelegate::DidCreateDirectory, |
| weak_factory_.GetWeakPtr(), src_url, callback)); |
| } |
| |
| void CopyOrMoveOperationDelegate::DidCreateDirectory( |
| const FileSystemURL& src_url, |
| const StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (!progress_callback_.is_null() && error == base::PLATFORM_FILE_OK) |
| progress_callback_.Run(FileSystemOperation::END_COPY_ENTRY, src_url, 0); |
| |
| callback.Run(error); |
| } |
| |
| void CopyOrMoveOperationDelegate::DidRemoveSourceForMove( |
| const StatusCallback& callback, |
| base::PlatformFileError error) { |
| if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) |
| error = base::PLATFORM_FILE_OK; |
| callback.Run(error); |
| } |
| |
| void CopyOrMoveOperationDelegate::OnCopyFileProgress( |
| const FileSystemURL& src_url, int64 size) { |
| if (!progress_callback_.is_null()) |
| progress_callback_.Run(FileSystemOperation::PROGRESS, src_url, size); |
| } |
| |
| FileSystemURL CopyOrMoveOperationDelegate::CreateDestURL( |
| const FileSystemURL& src_url) const { |
| DCHECK_EQ(src_root_.type(), src_url.type()); |
| DCHECK_EQ(src_root_.origin(), src_url.origin()); |
| |
| base::FilePath relative = dest_root_.virtual_path(); |
| src_root_.virtual_path().AppendRelativePath(src_url.virtual_path(), |
| &relative); |
| return file_system_context()->CreateCrackedFileSystemURL( |
| dest_root_.origin(), |
| dest_root_.mount_type(), |
| relative); |
| } |
| |
| } // namespace fileapi |