summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorhidehiko@chromium.org <hidehiko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-26 07:37:43 +0000
committerhidehiko@chromium.org <hidehiko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-26 07:37:43 +0000
commit3fd8f7a6c3d6fde861d2c50d710ee924b988fc3e (patch)
tree9068f327cdd52bdb7f1249963843f3b708712725 /webkit
parent9afdaef1886181705ca1008a8cf62a0dca5a2e62 (diff)
downloadchromium_src-3fd8f7a6c3d6fde861d2c50d710ee924b988fc3e.zip
chromium_src-3fd8f7a6c3d6fde861d2c50d710ee924b988fc3e.tar.gz
chromium_src-3fd8f7a6c3d6fde861d2c50d710ee924b988fc3e.tar.bz2
Implement stream based cross file system copy.
This CL implements the FileStreamReader/Writer based cross file system copy operation. BUG=279278 TEST=Ran content_unittests and unit_tests Review URL: https://codereview.chromium.org/23463048 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@225378 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/browser/fileapi/copy_or_move_operation_delegate.cc254
-rw-r--r--webkit/browser/fileapi/copy_or_move_operation_delegate.h45
-rw-r--r--webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc96
-rw-r--r--webkit/browser/fileapi/dragged_file_util_unittest.cc3
4 files changed, 390 insertions, 8 deletions
diff --git a/webkit/browser/fileapi/copy_or_move_operation_delegate.cc b/webkit/browser/fileapi/copy_or_move_operation_delegate.cc
index 845e33b..fed1abd 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"
@@ -289,8 +293,230 @@ 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 does not exist or the entry is
+ // 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);
+};
+
} // 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,
@@ -359,7 +585,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(
@@ -369,11 +594,28 @@ void CopyOrMoveOperationDelegate::ProcessFile(
return;
}
- impl = new SnapshotCopyOrMoveImpl(
- operation_runner(), operation_type_, src_url, dest_url, option_,
- 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, option_,
+ validator_factory,
+ base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
+ weak_factory_.GetWeakPtr(), src_url));
+ }
}
// Register the running task.
diff --git a/webkit/browser/fileapi/copy_or_move_operation_delegate.h b/webkit/browser/fileapi/copy_or_move_operation_delegate.h
index 1f82a34a..597e05e 100644
--- a/webkit/browser/fileapi/copy_or_move_operation_delegate.h
+++ b/webkit/browser/fileapi/copy_or_move_operation_delegate.h
@@ -9,15 +9,23 @@
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
#include "webkit/browser/fileapi/recursive_operation_delegate.h"
+namespace net {
+class DrainableIOBuffer;
+class IOBufferWithSize;
+}
+
namespace webkit_blob {
+class FileStreamReader;
class ShareableFileReference;
}
namespace fileapi {
class CopyOrMoveFileValidator;
+class FileStreamWriter;
// A delegate class for recursive copy or move operations.
class CopyOrMoveOperationDelegate
@@ -32,6 +40,43 @@ class CopyOrMoveOperationDelegate
OPERATION_MOVE
};
+ // Helper to copy a file by reader and writer streams.
+ // Export for testing.
+ class WEBKIT_STORAGE_BROWSER_EXPORT StreamCopyHelper {
+ public:
+ 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);
+ ~StreamCopyHelper();
+
+ void Run(const StatusCallback& callback);
+
+ private:
+ // Reads the content from the |reader_|.
+ void Read(const StatusCallback& callback);
+ void DidRead(const StatusCallback& callback, int result);
+
+ // Writes the content in |buffer| to |writer_|.
+ void Write(const StatusCallback& callback,
+ scoped_refptr<net::DrainableIOBuffer> buffer);
+ void DidWrite(const StatusCallback& callback,
+ scoped_refptr<net::DrainableIOBuffer> buffer, int result);
+
+ scoped_ptr<webkit_blob::FileStreamReader> reader_;
+ scoped_ptr<FileStreamWriter> writer_;
+ FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
+ scoped_refptr<net::IOBufferWithSize> io_buffer_;
+ int64 num_copied_bytes_;
+ base::Time last_progress_callback_invocation_time_;
+ base::TimeDelta min_progress_callback_invocation_span_;
+ base::WeakPtrFactory<StreamCopyHelper> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(StreamCopyHelper);
+ };
+
CopyOrMoveOperationDelegate(
FileSystemContext* file_system_context,
const FileSystemURL& src_root,
diff --git a/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
index 322341d..5c9534d 100644
--- a/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
+++ b/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
@@ -7,13 +7,17 @@
#include "base/basictypes.h"
#include "base/bind.h"
+#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/blob/file_stream_reader.h"
#include "webkit/browser/fileapi/async_file_test_helper.h"
#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h"
+#include "webkit/browser/fileapi/file_stream_writer.h"
#include "webkit/browser/fileapi/file_system_backend.h"
#include "webkit/browser/fileapi/file_system_context.h"
#include "webkit/browser/fileapi/file_system_operation.h"
@@ -114,6 +118,41 @@ void RecordProgressCallback(std::vector<ProgressRecord>* records,
records->push_back(record);
}
+void RecordFileProgressCallback(std::vector<int64>* records,
+ int64 progress) {
+ records->push_back(progress);
+}
+
+void AssignAndQuit(base::RunLoop* run_loop,
+ base::PlatformFileError* result_out,
+ base::PlatformFileError result) {
+ *result_out = result;
+ run_loop->Quit();
+}
+
+class ScopedThreadStopper {
+ public:
+ ScopedThreadStopper(base::Thread* thread) : thread_(thread) {
+ }
+
+ ~ScopedThreadStopper() {
+ if (thread_) {
+ // Give another chance for deleted streams to perform Close.
+ base::RunLoop run_loop;
+ thread_->message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&base::DoNothing), run_loop.QuitClosure());
+ run_loop.Run();
+ thread_->Stop();
+ }
+ }
+
+ bool is_valid() const { return thread_; }
+
+ private:
+ base::Thread* thread_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedThreadStopper);
+};
+
} // namespace
class CopyOrMoveOperationTestHelper {
@@ -124,7 +163,8 @@ class CopyOrMoveOperationTestHelper {
FileSystemType dest_type)
: origin_(origin),
src_type_(src_type),
- dest_type_(dest_type) {}
+ dest_type_(dest_type),
+ message_loop_(base::MessageLoop::TYPE_IO) {}
~CopyOrMoveOperationTestHelper() {
file_system_context_ = NULL;
@@ -662,4 +702,58 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, ProgressCallback) {
}
}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath source_path = temp_dir.path().AppendASCII("source");
+ const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ file_util::WriteFile(source_path, kTestData,
+ arraysize(kTestData) - 1); // Exclude trailing '\0'.
+
+ base::FilePath dest_path = temp_dir.path().AppendASCII("dest");
+ // LocalFileWriter requires the file exists. So create an empty file here.
+ file_util::WriteFile(dest_path, "", 0);
+
+ base::MessageLoop message_loop(base::MessageLoop::TYPE_IO);
+ base::Thread file_thread("file_thread");
+ ASSERT_TRUE(file_thread.Start());
+ ScopedThreadStopper thread_stopper(&file_thread);
+ ASSERT_TRUE(thread_stopper.is_valid());
+
+ scoped_refptr<base::MessageLoopProxy> task_runner =
+ file_thread.message_loop_proxy();
+
+ scoped_ptr<webkit_blob::FileStreamReader> reader(
+ webkit_blob::FileStreamReader::CreateForLocalFile(
+ task_runner.get(), source_path, 0, base::Time()));
+
+ scoped_ptr<FileStreamWriter> writer(
+ FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0));
+
+ std::vector<int64> progress;
+ CopyOrMoveOperationDelegate::StreamCopyHelper helper(
+ reader.Pass(), writer.Pass(),
+ 10, // buffer size
+ base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)),
+ base::TimeDelta()); // For testing, we need all the progress.
+
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error));
+ run_loop.Run();
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_EQ(5U, progress.size());
+ EXPECT_EQ(0, progress[0]);
+ EXPECT_EQ(10, progress[1]);
+ EXPECT_EQ(20, progress[2]);
+ EXPECT_EQ(30, progress[3]);
+ EXPECT_EQ(36, progress[4]);
+
+ std::string content;
+ ASSERT_TRUE(base::ReadFileToString(dest_path, &content));
+ EXPECT_EQ(kTestData, content);
+}
+
} // namespace fileapi
diff --git a/webkit/browser/fileapi/dragged_file_util_unittest.cc b/webkit/browser/fileapi/dragged_file_util_unittest.cc
index af15af8..f4c029b 100644
--- a/webkit/browser/fileapi/dragged_file_util_unittest.cc
+++ b/webkit/browser/fileapi/dragged_file_util_unittest.cc
@@ -89,7 +89,8 @@ FileSystemURL GetOtherURL(FileSystemContext* file_system_context,
class DraggedFileUtilTest : public testing::Test {
public:
- DraggedFileUtilTest() {}
+ DraggedFileUtilTest()
+ : message_loop_(base::MessageLoop::TYPE_IO) {}
virtual void SetUp() {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());