blob: 4bd1fc3ca51f948cb46752fb58518f057fa8c1d1 [file] [log] [blame]
// 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/file_writer_delegate.h"
#include "base/bind.h"
#include "base/callback.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_context.h"
#include "webkit/fileapi/file_system_operation.h"
#include "webkit/fileapi/file_system_operation_context.h"
#include "webkit/fileapi/file_system_path_manager.h"
#include "webkit/fileapi/file_system_quota_util.h"
#include "webkit/fileapi/quota_file_util.h"
namespace fileapi {
static const int kReadBufSize = 32768;
namespace {
typedef base::Callback<void(base::PlatformFileError /* error code */,
const base::PlatformFileInfo& /* file_info */)>
InitializeTaskCallback;
class InitializeTask : public base::RefCountedThreadSafe<InitializeTask> {
public:
InitializeTask(
base::PlatformFile file,
FileSystemOperationContext* context,
const InitializeTaskCallback& callback)
: origin_message_loop_proxy_(
base::MessageLoopProxy::current()),
error_code_(base::PLATFORM_FILE_OK),
file_(file),
context_(*context),
callback_(callback) {
DCHECK_EQ(false, callback.is_null());
}
bool Start(scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
const tracked_objects::Location& from_here) {
return message_loop_proxy->PostTask(
from_here,
base::Bind(&InitializeTask::ProcessOnTargetThread, this));
}
private:
friend class base::RefCountedThreadSafe<InitializeTask>;
void RunCallback() {
callback_.Run(error_code_, file_info_);
}
void ProcessOnTargetThread() {
DCHECK(context_.file_system_context());
FileSystemQuotaUtil* quota_util = context_.file_system_context()->
GetQuotaUtil(context_.src_type());
if (quota_util) {
DCHECK(quota_util->proxy());
quota_util->proxy()->StartUpdateOrigin(
context_.src_origin_url(), context_.src_type());
}
if (!base::GetPlatformFileInfo(file_, &file_info_))
error_code_ = base::PLATFORM_FILE_ERROR_FAILED;
origin_message_loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&InitializeTask::RunCallback, this));
}
scoped_refptr<base::MessageLoopProxy> origin_message_loop_proxy_;
base::PlatformFileError error_code_;
base::PlatformFile file_;
FileSystemOperationContext context_;
InitializeTaskCallback callback_;
base::PlatformFileInfo file_info_;
};
} // namespace (anonymous)
FileWriterDelegate::FileWriterDelegate(
FileSystemOperation* file_system_operation, int64 offset,
scoped_refptr<base::MessageLoopProxy> proxy)
: file_system_operation_(file_system_operation),
file_(base::kInvalidPlatformFileValue),
offset_(offset),
proxy_(proxy),
bytes_written_backlog_(0),
bytes_written_(0),
bytes_read_(0),
total_bytes_written_(0),
allowed_bytes_to_write_(0),
io_buffer_(new net::IOBufferWithSize(kReadBufSize)),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
}
FileWriterDelegate::~FileWriterDelegate() {
}
void FileWriterDelegate::OnGetFileInfoAndCallStartUpdate(
base::PlatformFileError error,
const base::PlatformFileInfo& file_info) {
if (error) {
OnError(error);
return;
}
int64 allowed_bytes_growth =
file_system_operation_context()->allowed_bytes_growth();
if (allowed_bytes_growth < 0)
allowed_bytes_growth = 0;
int64 overlap = file_info.size - offset_;
allowed_bytes_to_write_ = allowed_bytes_growth;
if (kint64max - overlap > allowed_bytes_growth)
allowed_bytes_to_write_ += overlap;
size_ = file_info.size;
file_stream_.reset(new net::FileStream(file_,
base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE |
base::PLATFORM_FILE_ASYNC));
request_->Start();
}
void FileWriterDelegate::Start(base::PlatformFile file,
net::URLRequest* request) {
file_ = file;
request_ = request;
scoped_refptr<InitializeTask> relay = new InitializeTask(
file_, file_system_operation_context(),
base::Bind(&FileWriterDelegate::OnGetFileInfoAndCallStartUpdate,
weak_factory_.GetWeakPtr()));
relay->Start(proxy_, FROM_HERE);
}
void FileWriterDelegate::OnReceivedRedirect(net::URLRequest* request,
const GURL& new_url,
bool* defer_redirect) {
NOTREACHED();
OnError(base::PLATFORM_FILE_ERROR_SECURITY);
}
void FileWriterDelegate::OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) {
NOTREACHED();
OnError(base::PLATFORM_FILE_ERROR_SECURITY);
}
void FileWriterDelegate::OnCertificateRequested(
net::URLRequest* request,
net::SSLCertRequestInfo* cert_request_info) {
NOTREACHED();
OnError(base::PLATFORM_FILE_ERROR_SECURITY);
}
void FileWriterDelegate::OnSSLCertificateError(net::URLRequest* request,
const net::SSLInfo& ssl_info,
bool is_hsts_host) {
NOTREACHED();
OnError(base::PLATFORM_FILE_ERROR_SECURITY);
}
void FileWriterDelegate::OnResponseStarted(net::URLRequest* request) {
DCHECK_EQ(request_, request);
// file_stream_->Seek() blocks the IO thread.
// See http://crbug.com/75548.
base::ThreadRestrictions::ScopedAllowIO allow_io;
if (!request->status().is_success()) {
OnError(base::PLATFORM_FILE_ERROR_FAILED);
return;
}
int64 error = file_stream_->Seek(net::FROM_BEGIN, offset_);
if (error != offset_) {
OnError(base::PLATFORM_FILE_ERROR_FAILED);
return;
}
Read();
}
void FileWriterDelegate::OnReadCompleted(net::URLRequest* request,
int bytes_read) {
DCHECK_EQ(request_, request);
if (!request->status().is_success()) {
OnError(base::PLATFORM_FILE_ERROR_FAILED);
return;
}
OnDataReceived(bytes_read);
}
void FileWriterDelegate::Read() {
bytes_written_ = 0;
bytes_read_ = 0;
if (request_->Read(io_buffer_.get(), io_buffer_->size(), &bytes_read_)) {
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&FileWriterDelegate::OnDataReceived,
weak_factory_.GetWeakPtr(), bytes_read_));
} else if (!request_->status().is_io_pending()) {
OnError(base::PLATFORM_FILE_ERROR_FAILED);
}
}
void FileWriterDelegate::OnDataReceived(int bytes_read) {
bytes_read_ = bytes_read;
if (!bytes_read_) { // We're done.
OnProgress(0, true);
} else {
// This could easily be optimized to rotate between a pool of buffers, so
// that we could read and write at the same time. It's not yet clear that
// it's necessary.
Write();
}
}
void FileWriterDelegate::Write() {
// allowed_bytes_to_write could be negative if the file size is
// greater than the current (possibly new) quota.
// (The UI should clear the entire origin data if the smaller quota size
// is set in general, though the UI/deletion code is not there yet.)
DCHECK(total_bytes_written_ <= allowed_bytes_to_write_ ||
allowed_bytes_to_write_ < 0);
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_,
static_cast<int>(bytes_to_write),
base::Bind(&FileWriterDelegate::OnDataWritten,
base::Unretained(this)));
if (write_response > 0)
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&FileWriterDelegate::OnDataWritten,
weak_factory_.GetWeakPtr(), write_response));
else if (net::ERR_IO_PENDING != write_response)
OnError(base::PLATFORM_FILE_ERROR_FAILED);
}
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
Write();
} else {
OnError(base::PLATFORM_FILE_ERROR_FAILED);
}
}
void FileWriterDelegate::OnError(base::PlatformFileError error) {
request_->set_delegate(NULL);
request_->Cancel();
if (quota_util()) {
quota_util()->proxy()->EndUpdateOrigin(
file_system_operation_context()->src_origin_url(),
file_system_operation_context()->src_type());
}
file_system_operation_->DidWrite(error, 0, true);
}
void FileWriterDelegate::OnProgress(int bytes_written, bool done) {
DCHECK(bytes_written + bytes_written_backlog_ >= bytes_written_backlog_);
if (quota_util() &&
bytes_written > 0 &&
total_bytes_written_ + bytes_written + offset_ > size_) {
int overlapped = 0;
if (total_bytes_written_ + offset_ < size_)
overlapped = size_ - total_bytes_written_ - offset_;
quota_util()->proxy()->UpdateOriginUsage(
file_system_operation_->file_system_context()->quota_manager_proxy(),
file_system_operation_context()->src_origin_url(),
file_system_operation_context()->src_type(),
bytes_written - overlapped);
}
static const int kMinProgressDelayMS = 200;
base::Time currentTime = base::Time::Now();
if (done || last_progress_event_time_.is_null() ||
(currentTime - last_progress_event_time_).InMilliseconds() >
kMinProgressDelayMS) {
bytes_written += bytes_written_backlog_;
last_progress_event_time_ = currentTime;
bytes_written_backlog_ = 0;
if (done && quota_util()) {
if (quota_util()) {
quota_util()->proxy()->EndUpdateOrigin(
file_system_operation_context()->src_origin_url(),
file_system_operation_context()->src_type());
}
}
file_system_operation_->DidWrite(
base::PLATFORM_FILE_OK, bytes_written, done);
return;
}
bytes_written_backlog_ += bytes_written;
}
FileSystemOperationContext*
FileWriterDelegate::file_system_operation_context() const {
DCHECK(file_system_operation_);
DCHECK(file_system_operation_->file_system_operation_context());
return file_system_operation_->file_system_operation_context();
}
FileSystemQuotaUtil* FileWriterDelegate::quota_util() const {
DCHECK(file_system_operation_);
DCHECK(file_system_operation_->file_system_context());
DCHECK(file_system_operation_->file_system_operation_context());
return file_system_operation_->file_system_context()->GetQuotaUtil(
file_system_operation_context()->src_type());
}
} // namespace fileapi