Commit f01de713 authored by kinaba@chromium.org's avatar kinaba@chromium.org

fileapi: FileWriter and LocalFileWriter.

We need these write stream interface for implementing generic cross-filesystem operation and to eventually deprecate OpenFileSystem::Write and PlatformFile related interface.

BUG=123993
TEST=unit_tests --gtest_filter='*LocalFileWriter*'

Review URL: http://codereview.chromium.org/10126004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133892 0039d316-1c4b-4281-b951-d872f2087c98
parent 917bb4bf
......@@ -2091,6 +2091,7 @@
'../webkit/fileapi/file_system_test_helper.h',
'../webkit/fileapi/file_system_url_request_job_unittest.cc',
'../webkit/fileapi/file_writer_delegate_unittest.cc',
'../webkit/fileapi/local_file_writer_unittest.cc',
'../webkit/fileapi/mock_file_system_options.cc',
'../webkit/fileapi/mock_file_system_options.h',
'../webkit/quota/mock_storage_client.cc',
......
// Copyright (c) 2012 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.
#ifndef WEBKIT_FILEAPI_FILE_WRITER_H_
#define WEBKIT_FILEAPI_FILE_WRITER_H_
#pragma once
#include "base/basictypes.h"
#include "net/base/completion_callback.h"
namespace net {
class IOBuffer;
}
namespace fileapi {
// A generic interface for writing to a file-like object.
class FileWriter {
public:
// Closes the file. If there's an in-flight operation, it is canceled (i.e.,
// the callback function associated with the operation is not called).
virtual ~FileWriter() {}
// Writes to the current cursor position asynchronously.
//
// Up to buf_len bytes will be written. (In other words, partial
// writes are allowed.) If the write completed synchronously, it returns
// the number of bytes written. If the operation could not be performed, it
// returns an error code. Otherwise, net::ERR_IO_PENDING is returned, and the
// callback will be run on the thread where Write() was called when the write
// has completed.
//
// It is invalid to call Write while there is an in-flight async operation.
virtual int Write(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) = 0;
// Cancels an in-flight async operation.
//
// If the cancel is finished synchronously, it returns net::OK. If the
// cancel could not be performed, it returns an error code. Especially when
// there is no in-flight operation, net::ERR_UNEXPECTED is returned.
// Otherwise, net::ERR_IO_PENDING is returned, and the callback will be run on
// the thread where Cancel() was called when the cancel has completed. It is
// invalid to call Cancel() more than once on the same async operation.
//
// In either case, the callback function passed to the in-flight async
// operation is dismissed immediately when Cancel() is called, and thus
// will never be called.
virtual int Cancel(const net::CompletionCallback& callback) = 0;
};
} // namespace fileapi
#endif // WEBKIT_FILEAPI_FILE_WRITER_H_
// Copyright (c) 2012 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/fileapi/local_file_writer.h"
#include "base/callback.h"
#include "base/message_loop.h"
#include "net/base/file_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
namespace fileapi {
namespace {
const int kOpenFlagsForWrite = base::PLATFORM_FILE_OPEN |
base::PLATFORM_FILE_WRITE |
base::PLATFORM_FILE_ASYNC;
} // namespace
LocalFileWriter::LocalFileWriter(const FilePath& file_path,
int64 initial_offset)
: file_path_(file_path),
initial_offset_(initial_offset),
has_pending_operation_(false),
weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {}
LocalFileWriter::~LocalFileWriter() {
// Invalidate weak pointers so that we won't receive any callbacks from
// in-flight stream operations, which might be triggered during the file close
// in the FileStream destructor.
weak_factory_.InvalidateWeakPtrs();
// FileStream's destructor closes the file safely, since we opened the file
// by its Open() method.
}
int LocalFileWriter::Write(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) {
DCHECK(!has_pending_operation_);
DCHECK(cancel_callback_.is_null());
has_pending_operation_ = true;
if (stream_impl_.get()) {
int result = InitiateWrite(buf, buf_len, callback);
if (result != net::ERR_IO_PENDING)
has_pending_operation_ = false;
return result;
}
return InitiateOpen(callback,
base::Bind(&LocalFileWriter::ReadyToWrite,
weak_factory_.GetWeakPtr(),
make_scoped_refptr(buf), buf_len, callback));
}
int LocalFileWriter::Cancel(const net::CompletionCallback& callback) {
if (!has_pending_operation_)
return net::ERR_UNEXPECTED;
DCHECK(!callback.is_null());
cancel_callback_ = callback;
return net::ERR_IO_PENDING;
}
int LocalFileWriter::InitiateOpen(const net::CompletionCallback& error_callback,
const base::Closure& main_operation) {
DCHECK(has_pending_operation_);
DCHECK(!stream_impl_.get());
stream_impl_.reset(new net::FileStream(NULL));
return stream_impl_->Open(file_path_,
kOpenFlagsForWrite,
base::Bind(&LocalFileWriter::DidOpen,
weak_factory_.GetWeakPtr(),
error_callback,
main_operation));
}
void LocalFileWriter::DidOpen(const net::CompletionCallback& error_callback,
const base::Closure& main_operation,
int result) {
DCHECK(has_pending_operation_);
DCHECK(stream_impl_.get());
if (CancelIfRequested())
return;
if (result != net::OK) {
has_pending_operation_ = false;
stream_impl_.reset(NULL);
error_callback.Run(result);
return;
}
InitiateSeek(error_callback, main_operation);
}
void LocalFileWriter::InitiateSeek(
const net::CompletionCallback& error_callback,
const base::Closure& main_operation) {
DCHECK(has_pending_operation_);
DCHECK(stream_impl_.get());
if (initial_offset_ == 0) {
// No need to seek.
main_operation.Run();
return;
}
int result = stream_impl_->Seek(net::FROM_BEGIN, initial_offset_,
base::Bind(&LocalFileWriter::DidSeek,
weak_factory_.GetWeakPtr(),
error_callback,
main_operation));
if (result != net::ERR_IO_PENDING) {
has_pending_operation_ = false;
error_callback.Run(result);
}
}
void LocalFileWriter::DidSeek(const net::CompletionCallback& error_callback,
const base::Closure& main_operation,
int64 result) {
DCHECK(has_pending_operation_);
if (CancelIfRequested())
return;
if (result != initial_offset_) {
// TODO(kinaba) add a more specific error code.
result = net::ERR_FAILED;
}
if (result < 0) {
has_pending_operation_ = false;
error_callback.Run(static_cast<int>(result));
return;
}
main_operation.Run();
}
void LocalFileWriter::ReadyToWrite(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) {
DCHECK(has_pending_operation_);
int result = InitiateWrite(buf, buf_len, callback);
if (result != net::ERR_IO_PENDING) {
has_pending_operation_ = false;
callback.Run(result);
}
}
int LocalFileWriter::InitiateWrite(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) {
DCHECK(has_pending_operation_);
DCHECK(stream_impl_.get());
return stream_impl_->Write(buf, buf_len,
base::Bind(&LocalFileWriter::DidWrite,
weak_factory_.GetWeakPtr(),
callback));
}
void LocalFileWriter::DidWrite(const net::CompletionCallback& callback,
int result) {
DCHECK(has_pending_operation_);
if (CancelIfRequested())
return;
has_pending_operation_ = false;
callback.Run(result);
}
bool LocalFileWriter::CancelIfRequested() {
DCHECK(has_pending_operation_);
if (cancel_callback_.is_null())
return false;
net::CompletionCallback pending_cancel = cancel_callback_;
has_pending_operation_ = false;
cancel_callback_.Reset();
pending_cancel.Run(net::OK);
return true;
}
} // namespace fileapi
// Copyright (c) 2012 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.
#ifndef WEBKIT_FILEAPI_LOCAL_FILE_WRITER_H_
#define WEBKIT_FILEAPI_LOCAL_FILE_WRITER_H_
#pragma once
#include <utility>
#include "base/compiler_specific.h"
#include "base/callback.h"
#include "base/file_path.h"
#include "base/platform_file.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "webkit/fileapi/file_writer.h"
namespace net {
class FileStream;
}
namespace fileapi {
// This class is a thin wrapper around net::FileStream for writing local files.
class LocalFileWriter : public FileWriter {
public:
// Create a writer for the existing file in the path |file_path| starting from
// |initial_offset|.
LocalFileWriter(const FilePath& file_path, int64 initial_offset);
virtual ~LocalFileWriter();
// FileWriter overrides.
virtual int Write(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) OVERRIDE;
virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE;
private:
// Opens |file_path_| and if it succeeds, proceeds to InitiateSeek().
// If failed, the error code is returned by calling |error_callback|.
int InitiateOpen(const net::CompletionCallback& error_callback,
const base::Closure& main_operation);
void DidOpen(const net::CompletionCallback& error_callback,
const base::Closure& main_operation,
int result);
// Seeks to |initial_offset_| and proceeds to |main_operation| if it succeeds.
// If failed, the error code is returned by calling |error_callback|.
void InitiateSeek(const net::CompletionCallback& error_callback,
const base::Closure& main_operation);
void DidSeek(const net::CompletionCallback& error_callback,
const base::Closure& main_operation,
int64 result);
// Passed as the |main_operation| of InitiateOpen() function.
void ReadyToWrite(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback);
// Writes asynchronously to the file.
int InitiateWrite(net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback);
void DidWrite(const net::CompletionCallback& callback, int result);
// Stops the in-flight operation and calls |cancel_callback_| if it has been
// set by Cancel() for the current operation.
bool CancelIfRequested();
// Initialization parameters.
const FilePath file_path_;
const int64 initial_offset_;
// Current states of the operation.
bool has_pending_operation_;
scoped_ptr<net::FileStream> stream_impl_;
net::CompletionCallback cancel_callback_;
base::WeakPtrFactory<LocalFileWriter> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(LocalFileWriter);
};
} // namespace fileapi
#endif // WEBKIT_FILEAPI_LOCAL_FILE_WRITER_H_
// Copyright (c) 2012 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.
// NOTE: These tests are run as part of "unit_tests" (in chrome/test) rather
// than as part of test_shell_tests because they rely on being able to
// instantiate a MessageLoop of type TYPE_IO. test_shell_tests uses TYPE_UI,
// which net::TestCompletionCallback doesn't allow.
#include "webkit/fileapi/local_file_writer.h"
#include <string>
#include "base/callback.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/scoped_temp_dir.h"
#include "base/memory/scoped_ptr.h"
#include "net/base/io_buffer.h"
#include "net/base/test_completion_callback.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using fileapi::LocalFileWriter;
class LocalFileWriterTest : public testing::Test {
public:
LocalFileWriterTest() : message_loop_(MessageLoop::TYPE_IO) {}
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
protected:
FilePath Path(const std::string& name) {
return temp_dir_.path().AppendASCII(name);
}
int WriteStringToWriter(LocalFileWriter* writer, const std::string& data) {
scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer(data));
scoped_refptr<net::DrainableIOBuffer> drainable(
new net::DrainableIOBuffer(buffer, buffer->size()));
while (drainable->BytesRemaining() > 0) {
net::TestCompletionCallback callback;
int result = writer->Write(drainable, drainable->BytesRemaining(),
callback.callback());
if (result == net::ERR_IO_PENDING)
result = callback.WaitForResult();
if (result <= 0)
return result;
drainable->DidConsume(result);
}
return net::OK;
}
std::string GetFileContent(const FilePath& path) {
std::string content;
file_util::ReadFileToString(path, &content);
return content;
}
FilePath CreateFileWithContent(const std::string& name,
const std::string& data) {
FilePath path = Path(name);
file_util::WriteFile(path, data.c_str(), data.size());
return path;
}
private:
MessageLoop message_loop_;
ScopedTempDir temp_dir_;
};
void NeverCalled(int) {
ADD_FAILURE();
}
} // namespace
TEST_F(LocalFileWriterTest, Write) {
FilePath path = CreateFileWithContent("file_a", "");
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 0));
EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo"));
EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "bar"));
writer.reset();
MessageLoop::current()->RunAllPending();
EXPECT_TRUE(file_util::PathExists(path));
EXPECT_EQ("foobar", GetFileContent(path));
}
TEST_F(LocalFileWriterTest, WriteMiddle) {
FilePath path = CreateFileWithContent("file_a", "foobar");
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 2));
EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx"));
writer.reset();
MessageLoop::current()->RunAllPending();
EXPECT_TRUE(file_util::PathExists(path));
EXPECT_EQ("foxxxr", GetFileContent(path));
}
TEST_F(LocalFileWriterTest, WriteEnd) {
FilePath path = CreateFileWithContent("file_a", "foobar");
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 6));
EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx"));
writer.reset();
MessageLoop::current()->RunAllPending();
EXPECT_TRUE(file_util::PathExists(path));
EXPECT_EQ("foobarxxx", GetFileContent(path));
}
TEST_F(LocalFileWriterTest, WriteFailForNonexistingFile) {
FilePath path = Path("file_a");
ASSERT_FALSE(file_util::PathExists(path));
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 0));
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, WriteStringToWriter(writer.get(), "foo"));
writer.reset();
MessageLoop::current()->RunAllPending();
EXPECT_FALSE(file_util::PathExists(path));
}
TEST_F(LocalFileWriterTest, CancelBeforeOperation) {
FilePath path = Path("file_a");
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 0));
// Cancel immediately fails when there's no in-flight operation.
int cancel_result = writer->Cancel(base::Bind(&NeverCalled));
EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result);
}
TEST_F(LocalFileWriterTest, CancelAfterFinishedOperation) {
FilePath path = CreateFileWithContent("file_a", "");
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 0));
EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo"));
// Cancel immediately fails when there's no in-flight operation.
int cancel_result = writer->Cancel(base::Bind(&NeverCalled));
EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result);
writer.reset();
MessageLoop::current()->RunAllPending();
// Write operation is already completed.
EXPECT_TRUE(file_util::PathExists(path));
EXPECT_EQ("foo", GetFileContent(path));
}
TEST_F(LocalFileWriterTest, CancelWrite) {
FilePath path = CreateFileWithContent("file_a", "foobar");
scoped_ptr<LocalFileWriter> writer(new LocalFileWriter(path, 0));
scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer("xxx"));
int result = writer->Write(buffer, buffer->size(), base::Bind(&NeverCalled));
ASSERT_EQ(net::ERR_IO_PENDING, result);
net::TestCompletionCallback callback;
writer->Cancel(callback.callback());
int cancel_result = callback.WaitForResult();
EXPECT_EQ(net::OK, cancel_result);
}
......@@ -57,6 +57,7 @@
'file_system_util.h',
'file_util_helper.cc',
'file_util_helper.h',
'file_writer.h',
'file_writer_delegate.cc',
'file_writer_delegate.h',
'isolated_context.cc',
......@@ -67,6 +68,8 @@
'isolated_mount_point_provider.h',
'local_file_util.cc',
'local_file_util.h',
'local_file_writer.cc',
'local_file_writer.h',
'native_file_util.cc',
'native_file_util.h',
'obfuscated_file_util.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment