blob: 3d6686781ebb477182cfdad836857010bdbac536 [file] [log] [blame]
// 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 "storage/browser/fileapi/copy_or_move_operation_delegate.h"
#include <stdint.h>
#include <memory>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "storage/browser/blob/shareable_file_reference.h"
#include "storage/browser/fileapi/copy_or_move_file_validator.h"
#include "storage/browser/fileapi/file_observers.h"
#include "storage/browser/fileapi/file_stream_reader.h"
#include "storage/browser/fileapi/file_stream_writer.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "storage/common/fileapi/file_system_util.h"
namespace storage {
const int64_t kFlushIntervalInBytes = 10 << 20; // 10MB.
class CopyOrMoveOperationDelegate::CopyOrMoveImpl {
public:
virtual ~CopyOrMoveImpl() = default;
virtual void Run(
const CopyOrMoveOperationDelegate::StatusCallback& callback) = 0;
virtual void Cancel() = 0;
protected:
CopyOrMoveImpl() = default;
private:
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,
CopyOrMoveOperationDelegate::CopyOrMoveOption option,
const FileSystemOperation::CopyFileProgressCallback&
file_progress_callback)
: operation_runner_(operation_runner),
operation_type_(operation_type),
src_url_(src_url),
dest_url_(dest_url),
option_(option),
file_progress_callback_(file_progress_callback) {
}
void Run(
const CopyOrMoveOperationDelegate::StatusCallback& callback) override {
if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_MOVE) {
operation_runner_->MoveFileLocal(src_url_, dest_url_, option_, callback);
} else {
operation_runner_->CopyFileLocal(
src_url_, dest_url_, option_, file_progress_callback_, callback);
}
}
void Cancel() override {
// We can do nothing for the copy/move operation on a local file system.
// Assuming the operation is quickly done, it should be ok to just wait
// for the completion.
}
private:
FileSystemOperationRunner* operation_runner_;
CopyOrMoveOperationDelegate::OperationType operation_type_;
FileSystemURL src_url_;
FileSystemURL dest_url_;
CopyOrMoveOperationDelegate::CopyOrMoveOption option_;
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,
CopyOrMoveOperationDelegate::CopyOrMoveOption option,
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),
option_(option),
validator_factory_(validator_factory),
file_progress_callback_(file_progress_callback),
cancel_requested_(false),
weak_factory_(this) {
}
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));
}
void Cancel() override { cancel_requested_ = true; }
private:
void RunAfterCreateSnapshot(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error,
const base::File::Info& file_info,
const base::FilePath& platform_path,
scoped_refptr<storage::ShareableFileReference> file_ref) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::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, std::move(file_ref),
callback, base::File::FILE_OK);
return;
}
// Run pre write validation.
PreWriteValidation(
platform_path,
base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPreWriteValidation,
weak_factory_.GetWeakPtr(), platform_path, file_info,
std::move(file_ref), callback));
}
void RunAfterPreWriteValidation(
const base::FilePath& platform_path,
const base::File::Info& file_info,
scoped_refptr<storage::ShareableFileReference> file_ref,
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::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, std::move(file_ref),
callback));
}
void RunAfterCopyInForeignFile(
const base::File::Info& file_info,
scoped_refptr<storage::ShareableFileReference> file_ref,
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::FILE_OK) {
callback.Run(error);
return;
}
file_progress_callback_.Run(file_info.size);
if (option_ == FileSystemOperation::OPTION_NONE) {
RunAfterTouchFile(callback, base::File::FILE_OK);
return;
}
operation_runner_->TouchFile(
dest_url_, base::Time::Now() /* last_access */,
file_info.last_modified,
base::Bind(&SnapshotCopyOrMoveImpl::RunAfterTouchFile,
weak_factory_.GetWeakPtr(), callback));
}
void RunAfterTouchFile(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
// Even if TouchFile is failed, just ignore it.
if (cancel_requested_) {
callback.Run(base::File::FILE_ERROR_ABORT);
return;
}
// |validator_| is NULL when the destination filesystem does not do
// validation.
if (!validator_) {
// No validation is needed.
RunAfterPostWriteValidation(callback, base::File::FILE_OK);
return;
}
PostWriteValidation(
base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPostWriteValidation,
weak_factory_.GetWeakPtr(), callback));
}
void RunAfterPostWriteValidation(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
if (cancel_requested_) {
callback.Run(base::File::FILE_ERROR_ABORT);
return;
}
if (error != base::File::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::File::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::File::Error error) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error == base::File::FILE_ERROR_NOT_FOUND)
error = base::File::FILE_OK;
callback.Run(error);
}
void DidRemoveDestForError(
base::File::Error prior_error,
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
if (error != base::File::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::File::Error error,
const base::File::Info& file_info,
const base::FilePath& platform_path,
scoped_refptr<storage::ShareableFileReference> file_ref) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::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(), std::move(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(
scoped_refptr<storage::ShareableFileReference> file_ref,
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
callback.Run(error);
}
FileSystemOperationRunner* operation_runner_;
CopyOrMoveOperationDelegate::OperationType operation_type_;
FileSystemURL src_url_;
FileSystemURL dest_url_;
CopyOrMoveOperationDelegate::CopyOrMoveOption option_;
CopyOrMoveFileValidatorFactory* validator_factory_;
std::unique_ptr<CopyOrMoveFileValidator> validator_;
FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
bool cancel_requested_;
base::WeakPtrFactory<SnapshotCopyOrMoveImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(SnapshotCopyOrMoveImpl);
};
// The size of buffer for StreamCopyHelper.
const int kReadBufferSize = 32768;
// To avoid too many progress callbacks, it should be called less
// frequently than 50ms.
const int kMinProgressCallbackInvocationSpanInMilliseconds = 50;
// Specifically for cross file system copy/move operation, this class uses
// stream reader and writer for copying. Validator is not supported, so if
// necessary SnapshotCopyOrMoveImpl should be used.
class StreamCopyOrMoveImpl
: public CopyOrMoveOperationDelegate::CopyOrMoveImpl {
public:
StreamCopyOrMoveImpl(
FileSystemOperationRunner* operation_runner,
FileSystemContext* file_system_context,
CopyOrMoveOperationDelegate::OperationType operation_type,
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOperationDelegate::CopyOrMoveOption option,
std::unique_ptr<storage::FileStreamReader> reader,
std::unique_ptr<FileStreamWriter> writer,
const FileSystemOperation::CopyFileProgressCallback&
file_progress_callback)
: operation_runner_(operation_runner),
file_system_context_(file_system_context),
operation_type_(operation_type),
src_url_(src_url),
dest_url_(dest_url),
option_(option),
reader_(std::move(reader)),
writer_(std::move(writer)),
file_progress_callback_(file_progress_callback),
cancel_requested_(false),
weak_factory_(this) {}
void Run(
const CopyOrMoveOperationDelegate::StatusCallback& callback) override {
// Reader can be created even if the entry does not exist or the entry is
// a directory. To check errors before destination file creation,
// check metadata first.
operation_runner_->GetMetadata(
src_url_, FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::Bind(&StreamCopyOrMoveImpl::RunAfterGetMetadataForSource,
weak_factory_.GetWeakPtr(), callback));
}
void Cancel() override {
cancel_requested_ = true;
if (copy_helper_)
copy_helper_->Cancel();
}
private:
void NotifyOnStartUpdate(const FileSystemURL& url) {
if (file_system_context_->GetUpdateObservers(url.type())) {
file_system_context_->GetUpdateObservers(url.type())
->Notify(&FileUpdateObserver::OnStartUpdate, url);
}
}
void NotifyOnModifyFile(const FileSystemURL& url) {
if (file_system_context_->GetChangeObservers(url.type())) {
file_system_context_->GetChangeObservers(url.type())
->Notify(&FileChangeObserver::OnModifyFile, url);
}
}
void NotifyOnEndUpdate(const FileSystemURL& url) {
if (file_system_context_->GetUpdateObservers(url.type())) {
file_system_context_->GetUpdateObservers(url.type())
->Notify(&FileUpdateObserver::OnEndUpdate, url);
}
}
void RunAfterGetMetadataForSource(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error,
const base::File::Info& file_info) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::FILE_OK) {
callback.Run(error);
return;
}
if (file_info.is_directory) {
// If not a directory, failed with appropriate error code.
callback.Run(base::File::FILE_ERROR_NOT_A_FILE);
return;
}
// To use FileStreamWriter, we need to ensure the destination file exists.
operation_runner_->CreateFile(
dest_url_,
true /* exclusive */,
base::Bind(&StreamCopyOrMoveImpl::RunAfterCreateFileForDestination,
weak_factory_.GetWeakPtr(),
callback,
file_info.last_modified));
}
void RunAfterCreateFileForDestination(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
const base::Time& last_modified,
base::File::Error error) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
// This conversion is to return the consistent status code with
// FileSystemFileUtil::Copy.
if (error == base::File::FILE_ERROR_NOT_A_FILE)
error = base::File::FILE_ERROR_INVALID_OPERATION;
if (error != base::File::FILE_OK &&
error != base::File::FILE_ERROR_EXISTS) {
callback.Run(error);
return;
}
if (error == base::File::FILE_ERROR_EXISTS) {
operation_runner_->Truncate(
dest_url_,
0 /* length */,
base::Bind(&StreamCopyOrMoveImpl::RunAfterTruncateForDestination,
weak_factory_.GetWeakPtr(),
callback,
last_modified));
return;
}
RunAfterTruncateForDestination(
callback, last_modified, base::File::FILE_OK);
}
void RunAfterTruncateForDestination(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
const base::Time& last_modified,
base::File::Error error) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::FILE_OK) {
callback.Run(error);
return;
}
NotifyOnStartUpdate(dest_url_);
DCHECK(!copy_helper_);
copy_helper_.reset(new CopyOrMoveOperationDelegate::StreamCopyHelper(
std::move(reader_), std::move(writer_),
dest_url_.mount_option().flush_policy(), kReadBufferSize,
file_progress_callback_,
base::TimeDelta::FromMilliseconds(
kMinProgressCallbackInvocationSpanInMilliseconds)));
copy_helper_->Run(
base::Bind(&StreamCopyOrMoveImpl::RunAfterStreamCopy,
weak_factory_.GetWeakPtr(), callback, last_modified));
}
void RunAfterStreamCopy(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
const base::Time& last_modified,
base::File::Error error) {
// Destruct StreamCopyHelper to close the destination file.
// This is important because some file system implementations update
// timestamps on file close and thus it should happen before we call
// TouchFile().
copy_helper_.reset();
NotifyOnModifyFile(dest_url_);
NotifyOnEndUpdate(dest_url_);
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error != base::File::FILE_OK) {
callback.Run(error);
return;
}
if (option_ == FileSystemOperation::OPTION_NONE) {
RunAfterTouchFile(callback, base::File::FILE_OK);
return;
}
operation_runner_->TouchFile(
dest_url_, base::Time::Now() /* last_access */, last_modified,
base::Bind(&StreamCopyOrMoveImpl::RunAfterTouchFile,
weak_factory_.GetWeakPtr(), callback));
}
void RunAfterTouchFile(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
// Even if TouchFile is failed, just ignore it.
if (cancel_requested_) {
callback.Run(base::File::FILE_ERROR_ABORT);
return;
}
if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_COPY) {
callback.Run(base::File::FILE_OK);
return;
}
DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE, operation_type_);
// Remove the source for finalizing move operation.
operation_runner_->Remove(
src_url_, false /* recursive */,
base::Bind(&StreamCopyOrMoveImpl::RunAfterRemoveForMove,
weak_factory_.GetWeakPtr(), callback));
}
void RunAfterRemoveForMove(
const CopyOrMoveOperationDelegate::StatusCallback& callback,
base::File::Error error) {
if (cancel_requested_)
error = base::File::FILE_ERROR_ABORT;
if (error == base::File::FILE_ERROR_NOT_FOUND)
error = base::File::FILE_OK;
callback.Run(error);
}
FileSystemOperationRunner* operation_runner_;
scoped_refptr<FileSystemContext> file_system_context_;
CopyOrMoveOperationDelegate::OperationType operation_type_;
FileSystemURL src_url_;
FileSystemURL dest_url_;
CopyOrMoveOperationDelegate::CopyOrMoveOption option_;
std::unique_ptr<storage::FileStreamReader> reader_;
std::unique_ptr<FileStreamWriter> writer_;
FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
std::unique_ptr<CopyOrMoveOperationDelegate::StreamCopyHelper> copy_helper_;
bool cancel_requested_;
base::WeakPtrFactory<StreamCopyOrMoveImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(StreamCopyOrMoveImpl);
};
} // namespace
CopyOrMoveOperationDelegate::StreamCopyHelper::StreamCopyHelper(
std::unique_ptr<storage::FileStreamReader> reader,
std::unique_ptr<FileStreamWriter> writer,
storage::FlushPolicy flush_policy,
int buffer_size,
const FileSystemOperation::CopyFileProgressCallback& file_progress_callback,
const base::TimeDelta& min_progress_callback_invocation_span)
: reader_(std::move(reader)),
writer_(std::move(writer)),
flush_policy_(flush_policy),
file_progress_callback_(file_progress_callback),
io_buffer_(new net::IOBufferWithSize(buffer_size)),
num_copied_bytes_(0),
previous_flush_offset_(0),
min_progress_callback_invocation_span_(
min_progress_callback_invocation_span),
cancel_requested_(false),
weak_factory_(this) {}
CopyOrMoveOperationDelegate::StreamCopyHelper::~StreamCopyHelper() = default;
void CopyOrMoveOperationDelegate::StreamCopyHelper::Run(
const StatusCallback& callback) {
file_progress_callback_.Run(0);
last_progress_callback_invocation_time_ = base::Time::Now();
Read(callback);
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel() {
cancel_requested_ = true;
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::Read(
const StatusCallback& callback) {
int result = reader_->Read(
io_buffer_.get(), io_buffer_->size(),
base::Bind(&StreamCopyHelper::DidRead,
weak_factory_.GetWeakPtr(), callback));
if (result != net::ERR_IO_PENDING)
DidRead(callback, result);
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::DidRead(
const StatusCallback& callback, int result) {
if (cancel_requested_) {
callback.Run(base::File::FILE_ERROR_ABORT);
return;
}
if (result < 0) {
callback.Run(NetErrorToFileError(result));
return;
}
if (result == 0) {
// Here is the EOF.
if (flush_policy_ == storage::FlushPolicy::FLUSH_ON_COMPLETION)
Flush(callback, true /* is_eof */);
else
callback.Run(base::File::FILE_OK);
return;
}
Write(callback, new net::DrainableIOBuffer(io_buffer_.get(), result));
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::Write(
const StatusCallback& callback,
scoped_refptr<net::DrainableIOBuffer> buffer) {
DCHECK_GT(buffer->BytesRemaining(), 0);
int result = writer_->Write(
buffer.get(), buffer->BytesRemaining(),
base::Bind(&StreamCopyHelper::DidWrite,
weak_factory_.GetWeakPtr(), callback, buffer));
if (result != net::ERR_IO_PENDING)
DidWrite(callback, buffer, result);
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::DidWrite(
const StatusCallback& callback,
scoped_refptr<net::DrainableIOBuffer> buffer,
int result) {
if (cancel_requested_) {
callback.Run(base::File::FILE_ERROR_ABORT);
return;
}
if (result < 0) {
callback.Run(NetErrorToFileError(result));
return;
}
buffer->DidConsume(result);
num_copied_bytes_ += result;
// Check the elapsed time since last |file_progress_callback_| invocation.
base::Time now = base::Time::Now();
if (now - last_progress_callback_invocation_time_ >=
min_progress_callback_invocation_span_) {
file_progress_callback_.Run(num_copied_bytes_);
last_progress_callback_invocation_time_ = now;
}
if (buffer->BytesRemaining() > 0) {
Write(callback, buffer);
return;
}
if (flush_policy_ == storage::FlushPolicy::FLUSH_ON_COMPLETION &&
(num_copied_bytes_ - previous_flush_offset_) > kFlushIntervalInBytes) {
Flush(callback, false /* not is_eof */);
} else {
Read(callback);
}
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::Flush(
const StatusCallback& callback, bool is_eof) {
int result = writer_->Flush(
base::Bind(&StreamCopyHelper::DidFlush,
weak_factory_.GetWeakPtr(), callback, is_eof));
if (result != net::ERR_IO_PENDING)
DidFlush(callback, is_eof, result);
}
void CopyOrMoveOperationDelegate::StreamCopyHelper::DidFlush(
const StatusCallback& callback, bool is_eof, int result) {
if (cancel_requested_) {
callback.Run(base::File::FILE_ERROR_ABORT);
return;
}
previous_flush_offset_ = num_copied_bytes_;
if (is_eof)
callback.Run(NetErrorToFileError(result));
else
Read(callback);
}
CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate(
FileSystemContext* file_system_context,
const FileSystemURL& src_root,
const FileSystemURL& dest_root,
OperationType operation_type,
CopyOrMoveOption option,
ErrorBehavior error_behavior,
const CopyProgressCallback& progress_callback,
const StatusCallback& callback)
: RecursiveOperationDelegate(file_system_context),
src_root_(src_root),
dest_root_(dest_root),
operation_type_(operation_type),
option_(option),
error_behavior_(error_behavior),
progress_callback_(progress_callback),
callback_(callback),
weak_factory_(this) {
same_file_system_ = src_root_.IsInSameFileSystem(dest_root_);
}
CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() = default;
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::File::FILE_ERROR_INVALID_OPERATION);
return;
}
if (same_file_system_ && src_root_.path() == dest_root_.path()) {
// In JS API this should return error, but we return success because Pepper
// wants to return success and we have a code path that returns error in
// Blink for JS (http://crbug.com/329517).
callback_.Run(base::File::FILE_OK);
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_, error_behavior_, 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, FileSystemURL(), 0);
}
FileSystemURL dest_url = CreateDestURL(src_url);
std::unique_ptr<CopyOrMoveImpl> impl;
if (same_file_system_ &&
(file_system_context()
->GetFileSystemBackend(src_url.type())
->HasInplaceCopyImplementation(src_url.type()) ||
operation_type_ == OPERATION_MOVE)) {
impl = std::make_unique<CopyOrMoveOnSameFileSystemImpl>(
operation_runner(), operation_type_, src_url, dest_url, option_,
base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
weak_factory_.GetWeakPtr(), src_url));
} else {
// Cross filesystem case.
base::File::Error error = base::File::FILE_ERROR_FAILED;
CopyOrMoveFileValidatorFactory* validator_factory =
file_system_context()->GetCopyOrMoveFileValidatorFactory(
dest_root_.type(), &error);
if (error != base::File::FILE_OK) {
if (!progress_callback_.is_null())
progress_callback_.Run(FileSystemOperation::ERROR_COPY_ENTRY, src_url,
dest_url, 0);
callback.Run(error);
return;
}
if (!validator_factory) {
std::unique_ptr<storage::FileStreamReader> reader =
file_system_context()->CreateFileStreamReader(
src_url, 0 /* offset */, storage::kMaximumLength, base::Time());
std::unique_ptr<FileStreamWriter> writer =
file_system_context()->CreateFileStreamWriter(dest_url, 0);
if (reader && writer) {
impl = std::make_unique<StreamCopyOrMoveImpl>(
operation_runner(), file_system_context(), operation_type_, src_url,
dest_url, option_, std::move(reader), std::move(writer),
base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
weak_factory_.GetWeakPtr(), src_url));
}
}
if (!impl) {
impl = std::make_unique<SnapshotCopyOrMoveImpl>(
operation_runner(), operation_type_, src_url, dest_url, option_,
validator_factory,
base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
weak_factory_.GetWeakPtr(), src_url));
}
}
// Register the running task.
CopyOrMoveImpl* impl_ptr = impl.get();
running_copy_set_[impl_ptr] = std::move(impl);
impl_ptr->Run(base::Bind(&CopyOrMoveOperationDelegate::DidCopyOrMoveFile,
weak_factory_.GetWeakPtr(), src_url, dest_url,
callback, impl_ptr));
}
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, FileSystemURL(), 0);
}
ProcessDirectoryInternal(src_url, CreateDestURL(src_url), callback);
}
void CopyOrMoveOperationDelegate::PostProcessDirectory(
const FileSystemURL& src_url,
const StatusCallback& callback) {
if (option_ == FileSystemOperation::OPTION_NONE) {
PostProcessDirectoryAfterTouchFile(
src_url, callback, base::File::FILE_OK);
return;
}
operation_runner()->GetMetadata(
src_url, FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::Bind(
&CopyOrMoveOperationDelegate::PostProcessDirectoryAfterGetMetadata,
weak_factory_.GetWeakPtr(), src_url, callback));
}
void CopyOrMoveOperationDelegate::OnCancel() {
// Request to cancel all running Copy/Move file.
for (auto& job : running_copy_set_)
job.first->Cancel();
}
void CopyOrMoveOperationDelegate::DidCopyOrMoveFile(
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
const StatusCallback& callback,
CopyOrMoveImpl* impl,
base::File::Error error) {
running_copy_set_.erase(impl);
if (!progress_callback_.is_null() && error != base::File::FILE_OK &&
error != base::File::FILE_ERROR_NOT_A_FILE)
progress_callback_.Run(FileSystemOperation::ERROR_COPY_ENTRY, src_url,
dest_url, 0);
if (!progress_callback_.is_null() && error == base::File::FILE_OK) {
progress_callback_.Run(
FileSystemOperation::END_COPY_ENTRY, src_url, dest_url, 0);
}
callback.Run(error);
}
void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot(
const StatusCallback& callback,
base::File::Error error) {
if (error == base::File::FILE_ERROR_NOT_A_DIRECTORY) {
callback_.Run(base::File::FILE_ERROR_INVALID_OPERATION);
return;
}
if (error != base::File::FILE_OK &&
error != base::File::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, dest_url, callback));
}
void CopyOrMoveOperationDelegate::DidCreateDirectory(
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
const StatusCallback& callback,
base::File::Error error) {
if (!progress_callback_.is_null() && error == base::File::FILE_OK) {
progress_callback_.Run(
FileSystemOperation::END_COPY_ENTRY, src_url, dest_url, 0);
}
callback.Run(error);
}
void CopyOrMoveOperationDelegate::PostProcessDirectoryAfterGetMetadata(
const FileSystemURL& src_url,
const StatusCallback& callback,
base::File::Error error,
const base::File::Info& file_info) {
if (error != base::File::FILE_OK) {
// Ignore the error, and run post process which should run after TouchFile.
PostProcessDirectoryAfterTouchFile(
src_url, callback, base::File::FILE_OK);
return;
}
operation_runner()->TouchFile(
CreateDestURL(src_url), base::Time::Now() /* last access */,
file_info.last_modified,
base::Bind(
&CopyOrMoveOperationDelegate::PostProcessDirectoryAfterTouchFile,
weak_factory_.GetWeakPtr(), src_url, callback));
}
void CopyOrMoveOperationDelegate::PostProcessDirectoryAfterTouchFile(
const FileSystemURL& src_url,
const StatusCallback& callback,
base::File::Error error) {
// Even if the TouchFile is failed, just ignore it.
if (operation_type_ == OPERATION_COPY) {
callback.Run(base::File::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::DidRemoveSourceForMove(
const StatusCallback& callback,
base::File::Error error) {
if (error == base::File::FILE_ERROR_NOT_FOUND)
error = base::File::FILE_OK;
callback.Run(error);
}
void CopyOrMoveOperationDelegate::OnCopyFileProgress(
const FileSystemURL& src_url,
int64_t size) {
if (!progress_callback_.is_null()) {
progress_callback_.Run(
FileSystemOperation::PROGRESS, src_url, FileSystemURL(), 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 storage