diff options
author | dmikurube@chromium.org <dmikurube@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-21 05:04:51 +0000 |
---|---|---|
committer | dmikurube@chromium.org <dmikurube@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-21 05:04:51 +0000 |
commit | 078c6971a60c9a375e433ded4399a23a57382053 (patch) | |
tree | 94b9294bcb406857553f34cb4520a89e4231a5ee | |
parent | cbf61bccc4488871222e616065b1104e57c5e9ac (diff) | |
download | chromium_src-078c6971a60c9a375e433ded4399a23a57382053.zip chromium_src-078c6971a60c9a375e433ded4399a23a57382053.tar.gz chromium_src-078c6971a60c9a375e433ded4399a23a57382053.tar.bz2 |
Add "allowed growth" for writing operations, such as copy, move, write and truncate.
A writing operation which increases the file size more than "allowed growth" results in PLATFORM_FILE_ERROR_NO_SPACE.
BUG=74841
TEST=FileWriterDelegateTest.*,QuotaFileUtilTest.*,FileSystemOperationTest.*
Review URL: http://codereview.chromium.org/6725021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@82441 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | webkit/fileapi/file_system_file_util.cc | 31 | ||||
-rw-r--r-- | webkit/fileapi/file_system_file_util.h | 23 | ||||
-rw-r--r-- | webkit/fileapi/file_system_operation.cc | 7 | ||||
-rw-r--r-- | webkit/fileapi/file_system_operation_context.h | 7 | ||||
-rw-r--r-- | webkit/fileapi/file_writer_delegate.cc | 50 | ||||
-rw-r--r-- | webkit/fileapi/file_writer_delegate.h | 19 | ||||
-rw-r--r-- | webkit/fileapi/file_writer_delegate_unittest.cc | 257 | ||||
-rw-r--r-- | webkit/fileapi/quota_file_util.cc | 82 | ||||
-rw-r--r-- | webkit/fileapi/quota_file_util.h | 43 | ||||
-rw-r--r-- | webkit/fileapi/quota_file_util_unittest.cc | 303 | ||||
-rw-r--r-- | webkit/fileapi/webkit_fileapi.gypi | 2 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gypi | 1 |
13 files changed, 789 insertions, 37 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 395aaf3..cbf8ea0 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1924,6 +1924,7 @@ '../webkit/fileapi/file_system_dir_url_request_job_unittest.cc', '../webkit/fileapi/file_system_operation_write_unittest.cc', '../webkit/fileapi/file_system_url_request_job_unittest.cc', + '../webkit/fileapi/file_writer_delegate_unittest.cc', ], 'conditions': [ ['p2p_apis==1', { diff --git a/webkit/fileapi/file_system_file_util.cc b/webkit/fileapi/file_system_file_util.cc index f012a33..3e4e6c6 100644 --- a/webkit/fileapi/file_system_file_util.cc +++ b/webkit/fileapi/file_system_file_util.cc @@ -142,9 +142,11 @@ PlatformFileError FileSystemFileUtil::Copy( return error_code; if (file_util::DirectoryExists(src_file_path)) - return CopyDirectory(context, src_file_path, dest_file_path); + return CopyOrMoveDirectory(context, src_file_path, dest_file_path, + true /* copy */); else - return CopyOrMoveFile(context, src_file_path, dest_file_path, true); + return CopyOrMoveFile(context, src_file_path, dest_file_path, + true /* copy */); } PlatformFileError FileSystemFileUtil::Move( @@ -158,15 +160,13 @@ PlatformFileError FileSystemFileUtil::Move( if (error_code != base::PLATFORM_FILE_OK) return error_code; - // TODO(dmikurube): ReplaceFile if in the same filesystem. + // TODO(dmikurube): ReplaceFile if in the same domain and filesystem type. if (file_util::DirectoryExists(src_file_path)) { - PlatformFileError error = - CopyDirectory(context, src_file_path, dest_file_path); - if (error != base::PLATFORM_FILE_OK) - return error; - return Delete(context, src_file_path, true); + return CopyOrMoveDirectory(context, src_file_path, dest_file_path, + false /* copy */); } else - return CopyOrMoveFile(context, src_file_path, dest_file_path, false); + return CopyOrMoveFile(context, src_file_path, dest_file_path, + false /* copy */); } PlatformFileError FileSystemFileUtil::Delete( @@ -285,10 +285,11 @@ PlatformFileError FileSystemFileUtil::CopyOrMoveFile( return base::PLATFORM_FILE_ERROR_FAILED; } -PlatformFileError FileSystemFileUtil::CopyDirectory( +PlatformFileError FileSystemFileUtil::CopyOrMoveDirectory( FileSystemOperationContext* context, const FilePath& src_file_path, - const FilePath& dest_file_path) { + const FilePath& dest_file_path, + bool copy) { // Re-check PerformCommonCheckAndPreparationForMoveAndCopy() by DCHECK. DCHECK(DirectoryExists(context, src_file_path)); DCHECK(DirectoryExists(context, dest_file_path.DirName())); @@ -317,11 +318,17 @@ PlatformFileError FileSystemFileUtil::CopyDirectory( } else { // CopyOrMoveFile here is the virtual overridden member function. PlatformFileError error = CopyOrMoveFile( - context, src_file_path_each, dest_file_path_each, true); + context, src_file_path_each, dest_file_path_each, copy); if (error != base::PLATFORM_FILE_OK) return error; } } + + if (!copy) { + PlatformFileError error = Delete(context, src_file_path, true); + if (error != base::PLATFORM_FILE_OK) + return error; + } return base::PLATFORM_FILE_OK; } diff --git a/webkit/fileapi/file_system_file_util.h b/webkit/fileapi/file_system_file_util.h index 9e62af3..79ad9aa 100644 --- a/webkit/fileapi/file_system_file_util.h +++ b/webkit/fileapi/file_system_file_util.h @@ -93,6 +93,13 @@ class FileSystemFileUtil { bool exclusive, bool recursive); + // Copies or moves a single file. + virtual PlatformFileError CopyOrMoveFile( + FileSystemOperationContext* context, + const FilePath& src_file_path, + const FilePath& dest_file_path, + bool copy); + // TODO(dmikurube): Make this method non-virtual if it's possible. // It conflicts with LocalFileSystemFileUtil for now. // @@ -175,22 +182,16 @@ class FileSystemFileUtil { FileSystemOperationContext* unused, const FilePath& file_path); - // Copies or moves a single file. - virtual PlatformFileError CopyOrMoveFile( - FileSystemOperationContext* context, - const FilePath& src_file_path, - const FilePath& dest_file_path, - bool copy); - - // Performs recursive copy by calling CopyOrMoveFile for individual files. - // Operations for recursive traversal are encapsulated in this method. + // Performs recursive copy or move by calling CopyOrMoveFile for individual + // files. Operations for recursive traversal are encapsulated in this method. // It assumes src_file_path and dest_file_path have passed // PerformCommonCheckAndPreparationForMoveAndCopy(). // This method is non-virtual, not to be overridden. - PlatformFileError CopyDirectory( + PlatformFileError CopyOrMoveDirectory( FileSystemOperationContext* context, const FilePath& src_file_path, - const FilePath& dest_file_path); + const FilePath& dest_file_path, + bool copy); // Returns a pointer to a new instance of AbstractFileEnumerator which is // implemented for each FileUtil subclass. The instance needs to be freed diff --git a/webkit/fileapi/file_system_operation.cc b/webkit/fileapi/file_system_operation.cc index 046a16f..12ec587 100644 --- a/webkit/fileapi/file_system_operation.cc +++ b/webkit/fileapi/file_system_operation.cc @@ -16,6 +16,7 @@ #include "webkit/fileapi/file_system_util.h" #include "webkit/fileapi/file_writer_delegate.h" #include "webkit/fileapi/local_file_system_file_util.h" +#include "webkit/fileapi/quota_file_util.h" namespace fileapi { @@ -34,6 +35,9 @@ FileSystemOperation::FileSystemOperation( #ifndef NDEBUG pending_operation_ = kOperationNone; #endif + // TODO(dmikurube): Read and set available bytes from the Quota Manager. + file_system_operation_context_.set_allowed_bytes_growth( + QuotaFileUtil::kNoLimit); } FileSystemOperation::~FileSystemOperation() { @@ -624,7 +628,8 @@ void FileSystemOperation::OnFileOpenedForWrite( delete this; return; } - file_writer_delegate_->Start(file.ReleaseValue(), blob_request_.get()); + file_writer_delegate_->Start(file.ReleaseValue(), blob_request_.get(), + file_system_operation_context_.allowed_bytes_growth(), proxy_); } bool FileSystemOperation::VerifyFileSystemPathForRead( diff --git a/webkit/fileapi/file_system_operation_context.h b/webkit/fileapi/file_system_operation_context.h index 994739a..dedc30e0 100644 --- a/webkit/fileapi/file_system_operation_context.h +++ b/webkit/fileapi/file_system_operation_context.h @@ -61,6 +61,12 @@ class FileSystemOperationContext { dest_type_ = dest_type; } + void set_allowed_bytes_growth(const int64& allowed_bytes_growth) { + allowed_bytes_growth_ = allowed_bytes_growth; + } + + int64 allowed_bytes_growth() const { return allowed_bytes_growth_; } + private: // This file_system_file_util_ is not "owned" by FileSystemOperationContext. // It is supposed to be a pointer to a singleton. @@ -71,6 +77,7 @@ class FileSystemOperationContext { GURL dest_origin_url_; FileSystemType src_type_; // Also used for any single-path operation. FileSystemType dest_type_; + int64 allowed_bytes_growth_; }; } // namespace fileapi diff --git a/webkit/fileapi/file_writer_delegate.cc b/webkit/fileapi/file_writer_delegate.cc index c0574fca..a06c80e 100644 --- a/webkit/fileapi/file_writer_delegate.cc +++ b/webkit/fileapi/file_writer_delegate.cc @@ -4,45 +4,65 @@ #include "webkit/fileapi/file_writer_delegate.h" +#include "base/file_util_proxy.h" #include "base/message_loop.h" #include "base/threading/thread_restrictions.h" #include "net/base/net_errors.h" #include "webkit/fileapi/file_system_operation.h" +#include "webkit/fileapi/quota_file_util.h" namespace fileapi { static const int kReadBufSize = 32768; FileWriterDelegate::FileWriterDelegate( - FileSystemOperation* file_system_operation, - int64 offset) + FileSystemOperation* file_system_operation, int64 offset) : file_system_operation_(file_system_operation), file_(base::kInvalidPlatformFileValue), offset_(offset), bytes_read_backlog_(0), bytes_written_(0), bytes_read_(0), + total_bytes_written_(0), + allowed_bytes_to_write_(0), io_buffer_(new net::IOBufferWithSize(kReadBufSize)), io_callback_(ALLOW_THIS_IN_INITIALIZER_LIST(this), &FileWriterDelegate::OnDataWritten), - method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), + callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { } FileWriterDelegate::~FileWriterDelegate() { } -void FileWriterDelegate::Start(base::PlatformFile file, - net::URLRequest* request) { - file_ = file; - request_ = request; +void FileWriterDelegate::OnGetFileInfoForWrite( + base::PlatformFileError error, + const base::PlatformFileInfo& file_info) { + if (allowed_bytes_growth_ != QuotaFileUtil::kNoLimit) + allowed_bytes_to_write_ = file_info.size - offset_ + allowed_bytes_growth_; + else + allowed_bytes_to_write_ = QuotaFileUtil::kNoLimit; file_stream_.reset( new net::FileStream( - file, + file_, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC)); request_->Start(); } +void FileWriterDelegate::Start(base::PlatformFile file, + net::URLRequest* request, + int64 allowed_bytes_growth, + scoped_refptr<base::MessageLoopProxy> proxy) { + file_ = file; + request_ = request; + allowed_bytes_growth_ = allowed_bytes_growth; + + base::FileUtilProxy::GetFileInfoFromPlatformFile( + proxy, file, callback_factory_.NewCallback( + &FileWriterDelegate::OnGetFileInfoForWrite)); +} + void FileWriterDelegate::OnReceivedRedirect( net::URLRequest* request, const GURL& new_url, bool* defer_redirect) { NOTREACHED(); @@ -120,8 +140,18 @@ void FileWriterDelegate::OnDataReceived(int bytes_read) { } void FileWriterDelegate::Write() { + DCHECK(total_bytes_written_ <= allowed_bytes_to_write_); + if (total_bytes_written_ >= allowed_bytes_to_write_) { + OnError(base::PLATFORM_FILE_ERROR_NO_SPACE); + return; + } + + int64 bytes_to_write = bytes_read_ - bytes_written_; + if (bytes_to_write > allowed_bytes_to_write_ - total_bytes_written_) + bytes_to_write = allowed_bytes_to_write_ - total_bytes_written_; + int write_response = file_stream_->Write(io_buffer_->data() + bytes_written_, - bytes_read_ - bytes_written_, + static_cast<int>(bytes_to_write), &io_callback_); if (write_response > 0) MessageLoop::current()->PostTask( @@ -136,6 +166,7 @@ void FileWriterDelegate::OnDataWritten(int write_response) { if (write_response > 0) { OnProgress(write_response, false); bytes_written_ += write_response; + total_bytes_written_ += write_response; if (bytes_written_ == bytes_read_) Read(); else @@ -146,6 +177,7 @@ void FileWriterDelegate::OnDataWritten(int write_response) { } void FileWriterDelegate::OnError(base::PlatformFileError error) { + request_->set_delegate(NULL); request_->Cancel(); file_system_operation_->DidWrite(error, 0, true); } diff --git a/webkit/fileapi/file_writer_delegate.h b/webkit/fileapi/file_writer_delegate.h index d2d306d..35fd21b 100644 --- a/webkit/fileapi/file_writer_delegate.h +++ b/webkit/fileapi/file_writer_delegate.h @@ -7,7 +7,9 @@ #include "base/file_path.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_callback_factory.h" #include "base/memory/scoped_ptr.h" +#include "base/message_loop_proxy.h" #include "base/platform_file.h" #include "base/task.h" #include "base/time.h" @@ -22,12 +24,13 @@ class FileSystemOperation; class FileWriterDelegate : public net::URLRequest::Delegate { public: - FileWriterDelegate( - FileSystemOperation* write_operation, - int64 offset); + FileWriterDelegate(FileSystemOperation* write_operation, int64 offset); virtual ~FileWriterDelegate(); - void Start(base::PlatformFile file, net::URLRequest* request); + void Start(base::PlatformFile file, + net::URLRequest* request, + int64 allowed_bytes_growth, + scoped_refptr<base::MessageLoopProxy> proxy); base::PlatformFile file() { return file_; } @@ -44,6 +47,9 @@ class FileWriterDelegate : public net::URLRequest::Delegate { virtual void OnReadCompleted(net::URLRequest* request, int bytes_read); private: + void OnGetFileInfoForWrite( + base::PlatformFileError error, + const base::PlatformFileInfo& file_info); void Read(); void OnDataReceived(int bytes_read); void Write(); @@ -53,16 +59,21 @@ class FileWriterDelegate : public net::URLRequest::Delegate { FileSystemOperation* file_system_operation_; base::PlatformFile file_; + base::PlatformFileInfo file_info_; int64 offset_; base::Time last_progress_event_time_; int bytes_read_backlog_; int bytes_written_; int bytes_read_; + int64 total_bytes_written_; + int64 allowed_bytes_growth_; + int64 allowed_bytes_to_write_; scoped_refptr<net::IOBufferWithSize> io_buffer_; scoped_ptr<net::FileStream> file_stream_; net::URLRequest* request_; net::CompletionCallbackImpl<FileWriterDelegate> io_callback_; ScopedRunnableMethodFactory<FileWriterDelegate> method_factory_; + base::ScopedCallbackFactory<FileWriterDelegate> callback_factory_; }; } // namespace fileapi diff --git a/webkit/fileapi/file_writer_delegate_unittest.cc b/webkit/fileapi/file_writer_delegate_unittest.cc new file mode 100644 index 0000000..38dffda --- /dev/null +++ b/webkit/fileapi/file_writer_delegate_unittest.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2011 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/unit) +// 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 URLRequest doesn't allow. +// + +#include <string> + +#include "base/file_util_proxy.h" +#include "base/memory/scoped_temp_dir.h" +#include "base/message_loop.h" +#include "googleurl/src/gurl.h" +#include "net/base/io_buffer.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_status.h" +#include "testing/platform_test.h" +#include "webkit/fileapi/file_system_callback_dispatcher.h" +#include "webkit/fileapi/file_system_operation.h" +#include "webkit/fileapi/file_writer_delegate.h" +#include "webkit/fileapi/quota_file_util.h" + +namespace fileapi { + +class FileWriterDelegateTest : public PlatformTest { + public: + FileWriterDelegateTest() + : loop_(MessageLoop::TYPE_IO), + status_(base::PLATFORM_FILE_OK), + bytes_written_(0), + complete_(false) {} + + void set_failure_status(base::PlatformFileError status) { + EXPECT_FALSE(complete_); + EXPECT_EQ(status_, base::PLATFORM_FILE_OK); + EXPECT_NE(status, base::PLATFORM_FILE_OK); + complete_ = true; + status_ = status; + } + base::PlatformFileError status() const { return status_; } + void add_bytes_written(int64 bytes, bool complete) { + bytes_written_ += bytes; + EXPECT_FALSE(complete_); + complete_ = complete; + } + int64 bytes_written() const { return bytes_written_; } + bool complete() const { return complete_; } + + protected: + virtual void SetUp(); + virtual void TearDown(); + + static net::URLRequest::ProtocolFactory Factory; + + scoped_ptr<FileWriterDelegate> file_writer_delegate_; + scoped_ptr<net::URLRequest> request_; + + MessageLoop loop_; + + ScopedTempDir dir_; + FilePath file_path_; + PlatformFile file_; + + // For post-operation status. + base::PlatformFileError status_; + int64 bytes_written_; + bool complete_; +}; + +namespace { + +static std::string g_content; + +class FileWriterDelegateTestJob : public net::URLRequestJob { + public: + FileWriterDelegateTestJob(net::URLRequest* request, + const std::string& content) + : net::URLRequestJob(request), + content_(content), + remaining_bytes_(content.length()), + cursor_(0) { + } + + void Start() { + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &FileWriterDelegateTestJob::NotifyHeadersComplete)); + } + + bool ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read) { + if (remaining_bytes_ < buf_size) + buf_size = static_cast<int>(remaining_bytes_); + + for (int i = 0; i < buf_size; ++i) + buf->data()[i] = content_[cursor_++]; + remaining_bytes_ -= buf_size; + + SetStatus(net::URLRequestStatus()); + *bytes_read = buf_size; + return true; + } + +private: + std::string content_; + int remaining_bytes_; + int cursor_; +}; + +class MockDispatcher : public FileSystemCallbackDispatcher { + public: + MockDispatcher(FileWriterDelegateTest* test) : test_(test) { } + + virtual void DidFail(base::PlatformFileError status) { + test_->set_failure_status(status); + MessageLoop::current()->Quit(); + } + + virtual void DidSucceed() { + ADD_FAILURE(); + } + + virtual void DidReadMetadata( + const base::PlatformFileInfo& info, + const FilePath& platform_path) { + ADD_FAILURE(); + } + + virtual void DidReadDirectory( + const std::vector<base::FileUtilProxy::Entry>& entries, + bool /* has_more */) { + ADD_FAILURE(); + } + + virtual void DidOpenFileSystem(const std::string&, const GURL&) { + ADD_FAILURE(); + } + + virtual void DidWrite(int64 bytes, bool complete) { + test_->add_bytes_written(bytes, complete); + if (complete) + MessageLoop::current()->Quit(); + } + + private: + FileWriterDelegateTest* test_; +}; + +} // namespace (anonymous) + +// static +net::URLRequestJob* FileWriterDelegateTest::Factory( + net::URLRequest* request, + const std::string& scheme) { + return new FileWriterDelegateTestJob(request, g_content); +} + +void FileWriterDelegateTest::SetUp() { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(dir_.path(), &file_path_)); + + bool created; + base::PlatformFileError error_code; + file_ = base::CreatePlatformFile( + file_path_, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_ASYNC, + &created, &error_code); + ASSERT_EQ(base::PLATFORM_FILE_OK, error_code); + + net::URLRequest::RegisterProtocolFactory("blob", &Factory); +} + +void FileWriterDelegateTest::TearDown() { + net::URLRequest::RegisterProtocolFactory("blob", NULL); + base::ClosePlatformFile(file_); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) { + GURL blob_url("blob:nolimit"); + g_content = std::string("The quick brown fox jumps over the lazy dog.\n"); + file_writer_delegate_.reset(new FileWriterDelegate( + new FileSystemOperation(new MockDispatcher(this), NULL, NULL, + QuotaFileUtil::GetInstance()), 0)); + request_.reset(new net::URLRequest(blob_url, file_writer_delegate_.get())); + + file_writer_delegate_->Start(file_, request_.get(), QuotaFileUtil::kNoLimit, + base::MessageLoopProxy::CreateForCurrentThread()); + MessageLoop::current()->Run(); + + file_writer_delegate_.reset(NULL); + + EXPECT_EQ(45, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) { + GURL blob_url("blob:just"); + g_content = std::string("The quick brown fox jumps over the lazy dog.\n"); + file_writer_delegate_.reset(new FileWriterDelegate( + new FileSystemOperation(new MockDispatcher(this), NULL, NULL, + QuotaFileUtil::GetInstance()), 0)); + request_.reset(new net::URLRequest(blob_url, file_writer_delegate_.get())); + + file_writer_delegate_->Start(file_, request_.get(), 45, + base::MessageLoopProxy::CreateForCurrentThread()); + MessageLoop::current()->Run(); + + file_writer_delegate_.reset(NULL); + + EXPECT_EQ(45, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); +} + +TEST_F(FileWriterDelegateTest, WriteFailureByQuota) { + GURL blob_url("blob:failure"); + g_content = std::string("The quick brown fox jumps over the lazy dog.\n"); + file_writer_delegate_.reset(new FileWriterDelegate( + new FileSystemOperation(new MockDispatcher(this), NULL, NULL, + QuotaFileUtil::GetInstance()), 0)); + request_.reset(new net::URLRequest(blob_url, file_writer_delegate_.get())); + + file_writer_delegate_->Start(file_, request_.get(), 44, + base::MessageLoopProxy::CreateForCurrentThread()); + MessageLoop::current()->Run(); + + file_writer_delegate_.reset(NULL); + + EXPECT_EQ(44, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_TRUE(complete()); +} + +TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) { + GURL blob_url("blob:zero"); + g_content = std::string(""); + file_writer_delegate_.reset(new FileWriterDelegate( + new FileSystemOperation(new MockDispatcher(this), NULL, NULL, + QuotaFileUtil::GetInstance()), 0)); + request_.reset(new net::URLRequest(blob_url, file_writer_delegate_.get())); + + file_writer_delegate_->Start(file_, request_.get(), 0, + base::MessageLoopProxy::CreateForCurrentThread()); + MessageLoop::current()->Run(); + + file_writer_delegate_.reset(NULL); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); +} + +} // namespace fileapi diff --git a/webkit/fileapi/quota_file_util.cc b/webkit/fileapi/quota_file_util.cc new file mode 100644 index 0000000..3a78e62 --- /dev/null +++ b/webkit/fileapi/quota_file_util.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2011 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/quota_file_util.h" + +#include "base/file_util.h" +#include "base/logging.h" + +namespace fileapi { + +const int64 QuotaFileUtil::kNoLimit = kint64max; + +namespace { + +// Checks if copying in the same filesystem can be performed. +// This method is not called for moving within a single filesystem. +static bool CanCopy( + const FilePath& src_file_path, + const FilePath& dest_file_path, + int64 allowed_bytes_growth, + int64* growth) { + base::PlatformFileInfo src_file_info; + if (!file_util::GetFileInfo(src_file_path, &src_file_info)) { + // Falling through to the actual copy/move operation. + return true; + } + base::PlatformFileInfo dest_file_info; + if (!file_util::GetFileInfo(dest_file_path, &dest_file_info)) + dest_file_info.size = 0; + if (allowed_bytes_growth != QuotaFileUtil::kNoLimit && + src_file_info.size - dest_file_info.size > allowed_bytes_growth) + return false; + if (growth != NULL) + *growth = src_file_info.size - dest_file_info.size; + + return true; +} + +} // namespace (anonymous) + +// static +QuotaFileUtil* QuotaFileUtil::GetInstance() { + return Singleton<QuotaFileUtil>::get(); +} + +base::PlatformFileError QuotaFileUtil::CopyOrMoveFile( + FileSystemOperationContext* fs_context, + const FilePath& src_file_path, + const FilePath& dest_file_path, + bool copy) { + // It assumes copy/move operations are always in the same fs currently. + // TODO(dmikurube): Do quota check if moving between different fs. + if (copy) { + int64 allowed_bytes_growth = fs_context->allowed_bytes_growth(); + // The third argument (growth) is not used for now. + if (!CanCopy(src_file_path, dest_file_path, allowed_bytes_growth, NULL)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + } + return FileSystemFileUtil::GetInstance()->CopyOrMoveFile(fs_context, + src_file_path, dest_file_path, copy); +} + +base::PlatformFileError QuotaFileUtil::Truncate( + FileSystemOperationContext* fs_context, + const FilePath& path, + int64 length) { + int64 allowed_bytes_growth = fs_context->allowed_bytes_growth(); + + if (allowed_bytes_growth != kNoLimit) { + base::PlatformFileInfo file_info; + if (!file_util::GetFileInfo(path, &file_info)) + return base::PLATFORM_FILE_ERROR_FAILED; + if (length - file_info.size > allowed_bytes_growth) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + } + + return FileSystemFileUtil::GetInstance()->Truncate( + fs_context, path, length); +} + +} // namespace fileapi diff --git a/webkit/fileapi/quota_file_util.h b/webkit/fileapi/quota_file_util.h new file mode 100644 index 0000000..e9ad6dd --- /dev/null +++ b/webkit/fileapi/quota_file_util.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 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_QUOTA_FILE_UTIL_H_ +#define WEBKIT_FILEAPI_QUOTA_FILE_UTIL_H_ + +#include "webkit/fileapi/file_system_file_util.h" +#include "webkit/fileapi/file_system_operation_context.h" +#pragma once + +namespace fileapi { + +class QuotaFileUtil : public FileSystemFileUtil { + public: + static QuotaFileUtil* GetInstance(); + ~QuotaFileUtil() {} + + static const int64 kNoLimit; + + base::PlatformFileError CopyOrMoveFile( + FileSystemOperationContext* fs_context, + const FilePath& src_file_path, + const FilePath& dest_file_path, + bool copy); + + // TODO(dmikurube): Charge some amount of quota for directories. + + base::PlatformFileError Truncate( + FileSystemOperationContext* fs_context, + const FilePath& path, + int64 length); + + friend struct DefaultSingletonTraits<QuotaFileUtil>; + DISALLOW_COPY_AND_ASSIGN(QuotaFileUtil); + + protected: + QuotaFileUtil() {} +}; + +} // namespace fileapi + +#endif // WEBKIT_FILEAPI_QUOTA_FILE_UTIL_H_ diff --git a/webkit/fileapi/quota_file_util_unittest.cc b/webkit/fileapi/quota_file_util_unittest.cc new file mode 100644 index 0000000..05fe840 --- /dev/null +++ b/webkit/fileapi/quota_file_util_unittest.cc @@ -0,0 +1,303 @@ +// Copyright (c) 2011 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/quota_file_util.h" + +#include "base/file_path.h" +#include "base/memory/scoped_callback_factory.h" +#include "base/memory/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/file_system_file_util.h" +#include "webkit/fileapi/file_system_types.h" +#include "webkit/fileapi/file_system_operation_context.h" + +using namespace fileapi; + +class QuotaFileUtilTest : public testing::Test { + public: + QuotaFileUtilTest() + : callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + } + + void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + } + + protected: + FileSystemOperationContext* Context() { + return new FileSystemOperationContext(NULL, QuotaFileUtil::GetInstance()); + } + + QuotaFileUtil* FileUtil() { + return QuotaFileUtil::GetInstance(); + } + + FilePath Path(const char *file_name) { + return data_dir_.path().AppendASCII(file_name); + } + + base::PlatformFileError CreateFile(const char* file_name, + base::PlatformFile* file_handle, bool* created) { + int file_flags = base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC; + + return FileUtil()->CreateOrOpen(Context(), + data_dir_.path().AppendASCII(file_name), + file_flags, file_handle, created); + } + + base::PlatformFileError EnsureFileExists(const char* file_name, + bool* created) { + return FileUtil()->EnsureFileExists(Context(), + data_dir_.path().AppendASCII(file_name), created); + } + + private: + ScopedTempDir data_dir_; + base::ScopedCallbackFactory<QuotaFileUtilTest> callback_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaFileUtilTest); +}; + +TEST_F(QuotaFileUtilTest, CreateAndClose) { + const char *file_name = "test_file"; + base::PlatformFile file_handle; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(file_name, &file_handle, &created)); + ASSERT_TRUE(created); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + FileUtil()->Close(Context(), file_handle)); +} + +TEST_F(QuotaFileUtilTest, EnsureFileExists) { + const char *file_name = "foobar"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + EXPECT_FALSE(created); +} + +TEST_F(QuotaFileUtilTest, Truncate) { + const char *file_name = "truncated"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + FileSystemOperationContext *truncate_context; + + truncate_context = Context(); + truncate_context->set_allowed_bytes_growth(1020); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(truncate_context, + Path(file_name), + 1020)); + + truncate_context = Context(); + truncate_context->set_allowed_bytes_growth(0); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(truncate_context, + Path(file_name), + 0)); + + truncate_context = Context(); + truncate_context->set_allowed_bytes_growth(1020); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + QuotaFileUtil::GetInstance()->Truncate(truncate_context, + Path(file_name), + 1021)); +} + +TEST_F(QuotaFileUtilTest, CopyFile) { + const char *from_file = "fromfile"; + const char *obstacle_file = "obstaclefile"; + const char *to_file1 = "tofile1"; + const char *to_file2 = "tofile2"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(obstacle_file, &created)); + ASSERT_TRUE(created); + FileSystemOperationContext *context; + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(from_file), + 1020)); + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(obstacle_file), + 1)); + + context = Context(); + context->set_allowed_bytes_growth(1020); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Copy(context, + Path(from_file), + Path(to_file1))); + + context = Context(); + context->set_allowed_bytes_growth(1019); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + QuotaFileUtil::GetInstance()->Copy(context, + Path(from_file), + Path(to_file2))); + + context = Context(); + context->set_allowed_bytes_growth(1019); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Copy(context, + Path(from_file), + Path(obstacle_file))); +} + +TEST_F(QuotaFileUtilTest, CopyDirectory) { + const char *from_dir = "fromdir"; + const char *from_file = "fromdir/fromfile"; + const char *to_dir1 = "todir1"; + const char *to_dir2 = "todir2"; + bool created; + FileSystemOperationContext *context; + + context = Context(); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->CreateDirectory(context, + Path(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(from_file), + 1020)); + + context = Context(); + context->set_allowed_bytes_growth(1020); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Copy(context, + Path(from_dir), + Path(to_dir1))); + + context = Context(); + context->set_allowed_bytes_growth(1019); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + QuotaFileUtil::GetInstance()->Copy(context, + Path(from_dir), + Path(to_dir2))); +} + +TEST_F(QuotaFileUtilTest, MoveFile) { + const char *from_file = "fromfile"; + const char *obstacle_file = "obstaclefile"; + const char *to_file = "tofile"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + FileSystemOperationContext *context; + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(from_file), + 1020)); + + context = Context(); + context->set_allowed_bytes_growth(0); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Move(context, + Path(from_file), + Path(to_file))); + + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(obstacle_file, &created)); + ASSERT_TRUE(created); + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(from_file), + 1020)); + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(obstacle_file), + 1)); + + context = Context(); + context->set_allowed_bytes_growth(0); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Move(context, + Path(from_file), + Path(obstacle_file))); +} + +TEST_F(QuotaFileUtilTest, MoveDirectory) { + const char *from_dir = "fromdir"; + const char *from_file = "fromdir/fromfile"; + const char *to_dir1 = "todir1"; + const char *to_dir2 = "todir2"; + bool created; + FileSystemOperationContext *context; + + context = Context(); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->CreateDirectory(context, + Path(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(from_file), + 1020)); + + context = Context(); + context->set_allowed_bytes_growth(1020); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Move(context, + Path(from_dir), + Path(to_dir1))); + + context = Context(); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->CreateDirectory(context, + Path(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context = Context(); + context->set_allowed_bytes_growth(QuotaFileUtil::kNoLimit); + ASSERT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Truncate(context, + Path(from_file), + 1020)); + + context = Context(); + context->set_allowed_bytes_growth(1019); + EXPECT_EQ(base::PLATFORM_FILE_OK, + QuotaFileUtil::GetInstance()->Move(context, + Path(from_dir), + Path(to_dir2))); +} diff --git a/webkit/fileapi/webkit_fileapi.gypi b/webkit/fileapi/webkit_fileapi.gypi index 70de6fc..258db99 100644 --- a/webkit/fileapi/webkit_fileapi.gypi +++ b/webkit/fileapi/webkit_fileapi.gypi @@ -48,6 +48,8 @@ 'local_file_system_file_util.h', 'sandbox_mount_point_provider.cc', 'sandbox_mount_point_provider.h', + 'quota_file_util.cc', + 'quota_file_util.h', 'webfilewriter_base.cc', 'webfilewriter_base.h', ], diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi index 8ce59cc..d843493 100644 --- a/webkit/tools/test_shell/test_shell.gypi +++ b/webkit/tools/test_shell/test_shell.gypi @@ -382,6 +382,7 @@ '../../fileapi/file_system_usage_tracker_unittest.cc', '../../fileapi/file_system_util_unittest.cc', '../../fileapi/sandbox_mount_point_provider_unittest.cc', + '../../fileapi/quota_file_util_unittest.cc', '../../fileapi/webfilewriter_base_unittest.cc', '../../glue/bookmarklet_unittest.cc', '../../glue/context_menu_unittest.cc', |