| // Copyright 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 "content/browser/renderer_host/pepper/quota_file_io.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/stl_util.h" |
| #include "base/task_runner_util.h" |
| |
| using base::PlatformFile; |
| using base::PlatformFileError; |
| using quota::StorageType; |
| |
| namespace content { |
| |
| namespace { |
| StorageType PPFileSystemTypeToQuotaStorageType(PP_FileSystemType type) { |
| switch (type) { |
| case PP_FILESYSTEMTYPE_LOCALPERSISTENT: |
| return quota::kStorageTypePersistent; |
| case PP_FILESYSTEMTYPE_LOCALTEMPORARY: |
| return quota::kStorageTypeTemporary; |
| default: |
| return quota::kStorageTypeUnknown; |
| } |
| NOTREACHED(); |
| return quota::kStorageTypeUnknown; |
| } |
| |
| int WriteAdapter(PlatformFile file, int64 offset, |
| scoped_ptr<char[]> data, int size) { |
| return base::WritePlatformFile(file, offset, data.get(), size); |
| } |
| |
| } // namespace |
| |
| class QuotaFileIO::PendingOperationBase { |
| public: |
| virtual ~PendingOperationBase() {} |
| |
| // Either one of Run() or DidFail() is called (the latter is called when |
| // there was more than one error during quota queries). |
| virtual void Run() = 0; |
| virtual void DidFail(PlatformFileError error) = 0; |
| |
| protected: |
| explicit PendingOperationBase(QuotaFileIO* quota_io) : quota_io_(quota_io) { |
| DCHECK(quota_io_); |
| quota_io_->WillUpdate(); |
| } |
| |
| QuotaFileIO* quota_io_; |
| }; |
| |
| class QuotaFileIO::WriteOperation : public PendingOperationBase { |
| public: |
| WriteOperation(QuotaFileIO* quota_io, |
| int64_t offset, |
| const char* buffer, |
| int32_t bytes_to_write, |
| const WriteCallback& callback) |
| : PendingOperationBase(quota_io), |
| offset_(offset), |
| bytes_to_write_(bytes_to_write), |
| callback_(callback), |
| finished_(false), |
| status_(base::PLATFORM_FILE_OK), |
| bytes_written_(0), |
| weak_factory_(this) { |
| // TODO(kinuko): Check the API convention if we really need to keep a copy |
| // of the buffer during the async write operations. |
| buffer_.reset(new char[bytes_to_write]); |
| memcpy(buffer_.get(), buffer, bytes_to_write); |
| } |
| virtual ~WriteOperation() {} |
| virtual void Run() OVERRIDE { |
| DCHECK(quota_io_); |
| if (quota_io_->CheckIfExceedsQuota(offset_ + bytes_to_write_)) { |
| DidFail(base::PLATFORM_FILE_ERROR_NO_SPACE); |
| return; |
| } |
| DCHECK(buffer_.get()); |
| |
| if (!base::PostTaskAndReplyWithResult( |
| quota_io_->delegate()->GetFileThreadMessageLoopProxy().get(), |
| FROM_HERE, |
| base::Bind(&WriteAdapter, |
| quota_io_->file_, |
| offset_, |
| base::Passed(&buffer_), |
| bytes_to_write_), |
| base::Bind(&WriteOperation::DidWrite, |
| weak_factory_.GetWeakPtr()))) { |
| DidFail(base::PLATFORM_FILE_ERROR_FAILED); |
| return; |
| } |
| } |
| |
| virtual void DidFail(PlatformFileError error) OVERRIDE { |
| DidFinish(error, 0); |
| } |
| |
| bool finished() const { return finished_; } |
| |
| virtual void WillRunCallback() { |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&WriteOperation::RunCallback, weak_factory_.GetWeakPtr())); |
| } |
| |
| private: |
| void DidWrite(int bytes_written) { |
| base::PlatformFileError error = bytes_written > 0 ? |
| base::PLATFORM_FILE_OK : base::PLATFORM_FILE_ERROR_FAILED; |
| DidFinish(error, bytes_written); |
| } |
| |
| void DidFinish(PlatformFileError status, int bytes_written) { |
| finished_ = true; |
| status_ = status; |
| bytes_written_ = bytes_written; |
| int64_t max_offset = |
| (status != base::PLATFORM_FILE_OK) ? 0 : offset_ + bytes_written; |
| // This may delete itself by calling RunCallback. |
| quota_io_->DidWrite(this, max_offset); |
| } |
| |
| virtual void RunCallback() { |
| DCHECK_EQ(false, callback_.is_null()); |
| callback_.Run(status_, bytes_written_); |
| delete this; |
| } |
| |
| const int64_t offset_; |
| scoped_ptr<char[]> buffer_; |
| const int32_t bytes_to_write_; |
| WriteCallback callback_; |
| bool finished_; |
| PlatformFileError status_; |
| int64_t bytes_written_; |
| base::WeakPtrFactory<WriteOperation> weak_factory_; |
| }; |
| |
| class QuotaFileIO::SetLengthOperation : public PendingOperationBase { |
| public: |
| SetLengthOperation(QuotaFileIO* quota_io, |
| int64_t length, |
| const StatusCallback& callback) |
| : PendingOperationBase(quota_io), |
| length_(length), |
| callback_(callback), |
| weak_factory_(this) {} |
| |
| virtual ~SetLengthOperation() {} |
| |
| virtual void Run() OVERRIDE { |
| DCHECK(quota_io_); |
| if (quota_io_->CheckIfExceedsQuota(length_)) { |
| DidFail(base::PLATFORM_FILE_ERROR_NO_SPACE); |
| return; |
| } |
| |
| if (!base::FileUtilProxy::Truncate( |
| quota_io_->delegate()->GetFileThreadMessageLoopProxy().get(), |
| quota_io_->file_, |
| length_, |
| base::Bind(&SetLengthOperation::DidFinish, |
| weak_factory_.GetWeakPtr()))) { |
| DidFail(base::PLATFORM_FILE_ERROR_FAILED); |
| return; |
| } |
| } |
| |
| virtual void DidFail(PlatformFileError error) OVERRIDE { |
| DidFinish(error); |
| } |
| |
| private: |
| void DidFinish(PlatformFileError status) { |
| quota_io_->DidSetLength(status, length_); |
| DCHECK_EQ(false, callback_.is_null()); |
| callback_.Run(status); |
| delete this; |
| } |
| |
| int64_t length_; |
| StatusCallback callback_; |
| base::WeakPtrFactory<SetLengthOperation> weak_factory_; |
| }; |
| |
| // QuotaFileIO -------------------------------------------------------------- |
| |
| QuotaFileIO::QuotaFileIO( |
| Delegate* delegate, |
| PlatformFile file, |
| const GURL& file_url, |
| PP_FileSystemType type) |
| : delegate_(delegate), |
| file_(file), |
| file_url_(file_url), |
| storage_type_(PPFileSystemTypeToQuotaStorageType(type)), |
| cached_file_size_(0), |
| cached_available_space_(0), |
| outstanding_quota_queries_(0), |
| outstanding_errors_(0), |
| max_written_offset_(0), |
| inflight_operations_(0), |
| weak_factory_(this) { |
| DCHECK_NE(base::kInvalidPlatformFileValue, file_); |
| DCHECK_NE(quota::kStorageTypeUnknown, storage_type_); |
| } |
| |
| QuotaFileIO::~QuotaFileIO() { |
| // Note that this doesn't dispatch pending callbacks. |
| STLDeleteContainerPointers(pending_operations_.begin(), |
| pending_operations_.end()); |
| STLDeleteContainerPointers(pending_callbacks_.begin(), |
| pending_callbacks_.end()); |
| } |
| |
| bool QuotaFileIO::Write( |
| int64_t offset, const char* buffer, int32_t bytes_to_write, |
| const WriteCallback& callback) { |
| if (bytes_to_write <= 0) |
| return false; |
| |
| WriteOperation* op = new WriteOperation( |
| this, offset, buffer, bytes_to_write, callback); |
| return RegisterOperationForQuotaChecks(op); |
| } |
| |
| bool QuotaFileIO::SetLength(int64_t length, const StatusCallback& callback) { |
| DCHECK(pending_operations_.empty()); |
| SetLengthOperation* op = new SetLengthOperation(this, length, callback); |
| return RegisterOperationForQuotaChecks(op); |
| } |
| |
| bool QuotaFileIO::RegisterOperationForQuotaChecks( |
| PendingOperationBase* op_ptr) { |
| scoped_ptr<PendingOperationBase> op(op_ptr); |
| if (pending_operations_.empty()) { |
| // This is the first pending quota check. Run querying the file size |
| // and available space. |
| outstanding_quota_queries_ = 0; |
| outstanding_errors_ = 0; |
| |
| // Query the file size. |
| ++outstanding_quota_queries_; |
| if (!base::FileUtilProxy::GetFileInfoFromPlatformFile( |
| delegate_->GetFileThreadMessageLoopProxy().get(), |
| file_, |
| base::Bind(&QuotaFileIO::DidQueryInfoForQuota, |
| weak_factory_.GetWeakPtr()))) { |
| // This makes the call fail synchronously; we do not fire the callback |
| // here but just delete the operation and return false. |
| return false; |
| } |
| |
| // Query the current available space. |
| ++outstanding_quota_queries_; |
| delegate_->QueryAvailableSpace( |
| file_url_.GetOrigin(), storage_type_, |
| base::Bind(&QuotaFileIO::DidQueryAvailableSpace, |
| weak_factory_.GetWeakPtr())); |
| } |
| pending_operations_.push_back(op.release()); |
| return true; |
| } |
| |
| void QuotaFileIO::DidQueryInfoForQuota( |
| base::PlatformFileError error_code, |
| const base::PlatformFileInfo& file_info) { |
| if (error_code != base::PLATFORM_FILE_OK) |
| ++outstanding_errors_; |
| cached_file_size_ = file_info.size; |
| DCHECK_GT(outstanding_quota_queries_, 0); |
| if (--outstanding_quota_queries_ == 0) |
| DidQueryForQuotaCheck(); |
| } |
| |
| void QuotaFileIO::DidQueryAvailableSpace(int64_t avail_space) { |
| cached_available_space_ = avail_space; |
| DCHECK_GT(outstanding_quota_queries_, 0); |
| if (--outstanding_quota_queries_ == 0) |
| DidQueryForQuotaCheck(); |
| } |
| |
| void QuotaFileIO::DidQueryForQuotaCheck() { |
| DCHECK(!pending_operations_.empty()); |
| DCHECK_GT(inflight_operations_, 0); |
| while (!pending_operations_.empty()) { |
| PendingOperationBase* op = pending_operations_.front(); |
| pending_operations_.pop_front(); |
| pending_callbacks_.push_back(op); |
| if (outstanding_errors_ > 0) { |
| op->DidFail(base::PLATFORM_FILE_ERROR_FAILED); |
| continue; |
| } |
| op->Run(); |
| } |
| } |
| |
| bool QuotaFileIO::CheckIfExceedsQuota(int64_t new_file_size) const { |
| DCHECK_GE(cached_file_size_, 0); |
| DCHECK_GE(cached_available_space_, 0); |
| return new_file_size - cached_file_size_ > cached_available_space_; |
| } |
| |
| void QuotaFileIO::WillUpdate() { |
| if (inflight_operations_++ == 0) { |
| delegate_->WillUpdateFile(file_url_); |
| DCHECK_EQ(0, max_written_offset_); |
| } |
| } |
| |
| void QuotaFileIO::DidWrite(WriteOperation* op, |
| int64_t written_offset_end) { |
| max_written_offset_ = std::max(max_written_offset_, written_offset_end); |
| DCHECK_GT(inflight_operations_, 0); |
| DCHECK(!pending_callbacks_.empty()); |
| // Fire callbacks for finished operations. |
| while (!pending_callbacks_.empty()) { |
| WriteOperation* op = static_cast<WriteOperation*>( |
| pending_callbacks_.front()); |
| if (!op->finished()) |
| break; |
| pending_callbacks_.pop_front(); |
| op->WillRunCallback(); |
| } |
| // If we have no more pending writes, notify the browser that we did |
| // update the file. |
| if (--inflight_operations_ == 0) { |
| DCHECK(pending_operations_.empty()); |
| int64_t growth = max_written_offset_ - cached_file_size_; |
| growth = growth < 0 ? 0 : growth; |
| |
| delegate_->DidUpdateFile(file_url_, growth); |
| max_written_offset_ = 0; |
| } |
| } |
| |
| void QuotaFileIO::DidSetLength(PlatformFileError error, int64_t new_file_size) { |
| DCHECK_EQ(1, inflight_operations_); |
| pending_callbacks_.pop_front(); |
| DCHECK(pending_callbacks_.empty()); |
| int64_t delta = (error != base::PLATFORM_FILE_OK) ? 0 : |
| new_file_size - cached_file_size_; |
| |
| delegate_->DidUpdateFile(file_url_, delta); |
| inflight_operations_ = 0; |
| } |
| |
| } // namespace content |