Chromium Code Reviews| Index: webkit/browser/fileapi/copy_or_move_operation_delegate.cc |
| diff --git a/webkit/browser/fileapi/copy_or_move_operation_delegate.cc b/webkit/browser/fileapi/copy_or_move_operation_delegate.cc |
| index aba17b55d7525b8a91390a7435e3cca45b89b566..ff3ee8e715a9b0023263a5995f8dcb2a47806447 100644 |
| --- a/webkit/browser/fileapi/copy_or_move_operation_delegate.cc |
| +++ b/webkit/browser/fileapi/copy_or_move_operation_delegate.cc |
| @@ -6,7 +6,11 @@ |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| +#include "net/base/io_buffer.h" |
| +#include "net/base/net_errors.h" |
| +#include "webkit/browser/blob/file_stream_reader.h" |
| #include "webkit/browser/fileapi/copy_or_move_file_validator.h" |
| +#include "webkit/browser/fileapi/file_stream_writer.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" |
| @@ -281,8 +285,263 @@ class SnapshotCopyOrMoveImpl |
| 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, |
| + CopyOrMoveOperationDelegate::OperationType operation_type, |
| + const FileSystemURL& src_url, |
| + const FileSystemURL& dest_url, |
| + scoped_ptr<webkit_blob::FileStreamReader> reader, |
| + scoped_ptr<FileStreamWriter> writer, |
| + const FileSystemOperation::CopyFileProgressCallback& |
| + file_progress_callback) |
| + : operation_runner_(operation_runner), |
| + operation_type_(operation_type), |
| + src_url_(src_url), |
| + dest_url_(dest_url), |
| + reader_(reader.Pass()), |
| + writer_(writer.Pass()), |
| + file_progress_callback_(file_progress_callback), |
| + weak_factory_(this) { |
| + } |
| + |
| + virtual void Run( |
| + const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE { |
| + // Reader can be created even if the entry is not exists or the entry is |
|
kinuko
2013/09/21 10:44:36
nit: is not exists -> does not exist
hidehiko
2013/09/23 08:18:10
Oops Done.
|
| + // a directory. To check errors before destination file creation, |
| + // check metadata first. |
| + operation_runner_->GetMetadata( |
| + src_url_, |
| + base::Bind(&StreamCopyOrMoveImpl::RunAfterGetMetadataForSource, |
| + weak_factory_.GetWeakPtr(), callback)); |
| + } |
| + |
| + private: |
| + void RunAfterGetMetadataForSource( |
| + const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| + base::PlatformFileError error, |
| + const base::PlatformFileInfo& file_info) { |
| + if (error != base::PLATFORM_FILE_OK) { |
| + callback.Run(error); |
| + return; |
| + } |
| + if (file_info.is_directory) { |
| + // If not a directory, failed with appropriate error code. |
| + callback.Run(base::PLATFORM_FILE_ERROR_NOT_A_FILE); |
| + return; |
| + } |
| + |
| + // To use FileStreamWriter, we need to ensure the destination file exists. |
| + operation_runner_->CreateFile( |
| + dest_url_, false /* exclusive */, |
| + base::Bind(&StreamCopyOrMoveImpl::RunAfterCreateFileForDestination, |
| + weak_factory_.GetWeakPtr(), callback)); |
| + } |
| + |
| + void RunAfterCreateFileForDestination( |
| + const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| + base::PlatformFileError error) { |
| + if (error != base::PLATFORM_FILE_OK) { |
| + callback.Run(error); |
| + return; |
| + } |
| + |
| + DCHECK(!copy_helper_); |
| + copy_helper_.reset( |
| + new CopyOrMoveOperationDelegate::StreamCopyHelper( |
| + reader_.Pass(), writer_.Pass(), |
| + kReadBufferSize, |
| + file_progress_callback_, |
| + base::TimeDelta::FromMilliseconds( |
| + kMinProgressCallbackInvocationSpanInMilliseconds))); |
| + copy_helper_->Run( |
| + base::Bind(&StreamCopyOrMoveImpl::RunAfterStreamCopy, |
| + weak_factory_.GetWeakPtr(), callback)); |
| + } |
| + |
| + void RunAfterStreamCopy( |
| + const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| + base::PlatformFileError error) { |
| + if (error != base::PLATFORM_FILE_OK) { |
| + callback.Run(error); |
| + 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_, false /* recursive */, |
| + base::Bind(&StreamCopyOrMoveImpl::RunAfterRemoveForMove, |
| + weak_factory_.GetWeakPtr(), callback)); |
| + } |
| + |
| + void RunAfterRemoveForMove( |
| + const CopyOrMoveOperationDelegate::StatusCallback& callback, |
| + base::PlatformFileError error) { |
| + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) |
| + error = base::PLATFORM_FILE_OK; |
| + callback.Run(error); |
| + } |
| + |
| + FileSystemOperationRunner* operation_runner_; |
| + CopyOrMoveOperationDelegate::OperationType operation_type_; |
| + FileSystemURL src_url_; |
| + FileSystemURL dest_url_; |
| + scoped_ptr<webkit_blob::FileStreamReader> reader_; |
| + scoped_ptr<FileStreamWriter> writer_; |
| + FileSystemOperation::CopyFileProgressCallback file_progress_callback_; |
| + scoped_ptr<CopyOrMoveOperationDelegate::StreamCopyHelper> copy_helper_; |
| + |
| + base::WeakPtrFactory<StreamCopyOrMoveImpl> weak_factory_; |
| + DISALLOW_COPY_AND_ASSIGN(StreamCopyOrMoveImpl); |
| +}; |
| + |
| +// Translates the net::Error to base::PlatformFileError. |
| +base::PlatformFileError NetErrorToPlatformFileError(int error) { |
|
kinuko
2013/09/21 10:44:36
Hmm.. could this be moved to webkit/common/fileapi
hidehiko
2013/09/23 08:18:10
Done.
|
| + switch (error) { |
| + case net::OK: |
| + return base::PLATFORM_FILE_OK; |
| + case net::ERR_ADDRESS_IN_USE: |
| + return base::PLATFORM_FILE_ERROR_IN_USE; |
| + case net::ERR_FILE_EXISTS: |
| + return base::PLATFORM_FILE_ERROR_EXISTS; |
| + case net::ERR_FILE_NOT_FOUND: |
| + return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| + case net::ERR_ACCESS_DENIED: |
| + return base::PLATFORM_FILE_ERROR_ACCESS_DENIED; |
| + case net::ERR_TOO_MANY_SOCKET_STREAMS: |
| + return base::PLATFORM_FILE_ERROR_TOO_MANY_OPENED; |
| + case net::ERR_OUT_OF_MEMORY: |
| + return base::PLATFORM_FILE_ERROR_NO_MEMORY; |
| + case net::ERR_FILE_NO_SPACE: |
| + return base::PLATFORM_FILE_ERROR_NO_SPACE; |
| + case net::ERR_INVALID_ARGUMENT: |
| + case net::ERR_INVALID_HANDLE: |
| + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; |
| + case net::ERR_ABORTED: |
| + case net::ERR_CONNECTION_ABORTED: |
| + return base::PLATFORM_FILE_ERROR_ABORT; |
| + case net::ERR_ADDRESS_INVALID: |
| + case net::ERR_INVALID_URL: |
| + return base::PLATFORM_FILE_ERROR_INVALID_URL; |
| + default: |
| + return base::PLATFORM_FILE_ERROR_FAILED; |
| + } |
| +} |
| + |
| } // namespace |
| +CopyOrMoveOperationDelegate::StreamCopyHelper::StreamCopyHelper( |
| + scoped_ptr<webkit_blob::FileStreamReader> reader, |
| + scoped_ptr<FileStreamWriter> writer, |
| + int buffer_size, |
| + const FileSystemOperation::CopyFileProgressCallback& |
| + file_progress_callback, |
| + const base::TimeDelta& min_progress_callback_invocation_span) |
| + : reader_(reader.Pass()), |
| + writer_(writer.Pass()), |
| + file_progress_callback_(file_progress_callback), |
| + io_buffer_(new net::IOBufferWithSize(buffer_size)), |
| + num_copied_bytes_(0), |
| + min_progress_callback_invocation_span_( |
| + min_progress_callback_invocation_span), |
| + weak_factory_(this) { |
| +} |
| + |
| +CopyOrMoveOperationDelegate::StreamCopyHelper::~StreamCopyHelper() { |
| +} |
| + |
| +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::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 (result < 0) { |
| + callback.Run(NetErrorToPlatformFileError(result)); |
| + return; |
| + } |
| + |
| + if (result == 0) { |
| + // Here is the EOF. |
| + callback.Run(base::PLATFORM_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 (result < 0) { |
| + callback.Run(NetErrorToPlatformFileError(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; |
| + } |
| + |
| + Read(callback); |
| +} |
| CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate( |
| FileSystemContext* file_system_context, |
| @@ -349,7 +608,6 @@ void CopyOrMoveOperationDelegate::ProcessFile( |
| 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( |
| @@ -359,11 +617,28 @@ void CopyOrMoveOperationDelegate::ProcessFile( |
| return; |
| } |
| - impl = new SnapshotCopyOrMoveImpl( |
| - operation_runner(), operation_type_, src_url, dest_url, |
| - validator_factory, |
| - base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, |
| - weak_factory_.GetWeakPtr(), src_url)); |
| + if (!validator_factory) { |
| + scoped_ptr<webkit_blob::FileStreamReader> reader = |
| + file_system_context()->CreateFileStreamReader( |
| + src_url, 0, base::Time()); |
| + scoped_ptr<FileStreamWriter> writer = |
| + file_system_context()->CreateFileStreamWriter(dest_url, 0); |
| + if (reader && writer) { |
| + impl = new StreamCopyOrMoveImpl( |
| + operation_runner(), operation_type_, src_url, dest_url, |
| + reader.Pass(), writer.Pass(), |
| + base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, |
| + weak_factory_.GetWeakPtr(), src_url)); |
| + } |
| + } |
| + |
| + if (!impl) { |
| + 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. |