| // Copyright (c) 2009 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/appcache/appcache_response.h" |
| |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/pickle.h" |
| #include "base/string_util.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/io_buffer.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "webkit/appcache/appcache_disk_cache.h" |
| #include "webkit/appcache/appcache_service.h" |
| |
| using disk_cache::Entry; |
| |
| namespace appcache { |
| |
| namespace { |
| |
| // Disk cache entry data indices. |
| enum { |
| kResponseInfoIndex, |
| kResponseContentIndex |
| }; |
| |
| // An IOBuffer that wraps a pickle's data. Ownership of the |
| // pickle is transfered to the WrappedPickleIOBuffer object. |
| class WrappedPickleIOBuffer : public net::WrappedIOBuffer { |
| public: |
| explicit WrappedPickleIOBuffer(const Pickle* pickle) : |
| net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())), |
| pickle_(pickle) { |
| DCHECK(pickle->data()); |
| } |
| |
| private: |
| ~WrappedPickleIOBuffer() {} |
| |
| scoped_ptr<const Pickle> pickle_; |
| }; |
| |
| } // anon namespace |
| |
| |
| // AppCacheResponseInfo ---------------------------------------------- |
| |
| AppCacheResponseInfo::AppCacheResponseInfo( |
| AppCacheService* service, const GURL& manifest_url, |
| int64 response_id, net::HttpResponseInfo* http_info, |
| int64 response_data_size) |
| : manifest_url_(manifest_url), response_id_(response_id), |
| http_response_info_(http_info), response_data_size_(response_data_size), |
| service_(service) { |
| DCHECK(http_info); |
| DCHECK(response_id != kNoResponseId); |
| service_->storage()->working_set()->AddResponseInfo(this); |
| } |
| |
| AppCacheResponseInfo::~AppCacheResponseInfo() { |
| service_->storage()->working_set()->RemoveResponseInfo(this); |
| } |
| |
| // HttpResponseInfoIOBuffer ------------------------------------------ |
| |
| HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer() |
| : response_data_size(kUnkownResponseDataSize) {} |
| |
| HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer(net::HttpResponseInfo* info) |
| : http_info(info), response_data_size(kUnkownResponseDataSize) {} |
| |
| HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() {} |
| |
| // AppCacheResponseIO ---------------------------------------------- |
| |
| AppCacheResponseIO::AppCacheResponseIO( |
| int64 response_id, AppCacheDiskCache* disk_cache) |
| : response_id_(response_id), disk_cache_(disk_cache), |
| entry_(NULL), buffer_len_(0), user_callback_(NULL), |
| ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), |
| ALLOW_THIS_IN_INITIALIZER_LIST(raw_callback_( |
| new net::CancelableCompletionCallback<AppCacheResponseIO>( |
| this, &AppCacheResponseIO::OnRawIOComplete))) { |
| } |
| |
| AppCacheResponseIO::~AppCacheResponseIO() { |
| raw_callback_->Cancel(); |
| if (entry_) |
| entry_->Close(); |
| } |
| |
| void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) { |
| MessageLoop::current()->PostTask(FROM_HERE, |
| method_factory_.NewRunnableMethod( |
| &AppCacheResponseIO::OnIOComplete, result)); |
| } |
| |
| void AppCacheResponseIO::InvokeUserCompletionCallback(int result) { |
| // Clear the user callback and buffers prior to invoking the callback |
| // so the caller can schedule additional operations in the callback. |
| buffer_ = NULL; |
| info_buffer_ = NULL; |
| net::CompletionCallback* temp_user_callback = user_callback_; |
| user_callback_ = NULL; |
| temp_user_callback->Run(result); |
| } |
| |
| void AppCacheResponseIO::ReadRaw(int index, int offset, |
| net::IOBuffer* buf, int buf_len) { |
| DCHECK(entry_); |
| raw_callback_->AddRef(); // Balanced in OnRawIOComplete. |
| int rv = entry_->ReadData(index, offset, buf, buf_len, raw_callback_); |
| if (rv != net::ERR_IO_PENDING) { |
| raw_callback_->Release(); |
| ScheduleIOCompletionCallback(rv); |
| } |
| } |
| |
| void AppCacheResponseIO::WriteRaw(int index, int offset, |
| net::IOBuffer* buf, int buf_len) { |
| DCHECK(entry_); |
| const bool kTruncate = true; |
| raw_callback_->AddRef(); // Balanced in OnRawIOComplete. |
| int rv = entry_->WriteData(index, offset, buf, buf_len, raw_callback_, |
| kTruncate); |
| if (rv != net::ERR_IO_PENDING) { |
| raw_callback_->Release(); |
| ScheduleIOCompletionCallback(rv); |
| } |
| } |
| |
| void AppCacheResponseIO::OnRawIOComplete(int result) { |
| raw_callback_->Release(); // Balance the AddRefs |
| OnIOComplete(result); |
| } |
| |
| |
| // AppCacheResponseReader ---------------------------------------------- |
| |
| AppCacheResponseReader::AppCacheResponseReader( |
| int64 response_id, AppCacheDiskCache* disk_cache) |
| : AppCacheResponseIO(response_id, disk_cache), |
| range_offset_(0), range_length_(kint32max), |
| read_position_(0) { |
| } |
| |
| AppCacheResponseReader::~AppCacheResponseReader() { |
| if (open_callback_) |
| open_callback_.release()->Cancel(); |
| } |
| |
| void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf, |
| net::CompletionCallback* callback) { |
| DCHECK(callback && !IsReadPending()); |
| DCHECK(info_buf && !info_buf->http_info.get()); |
| DCHECK(!buffer_.get() && !info_buffer_.get()); |
| |
| info_buffer_ = info_buf; |
| user_callback_ = callback; // cleared on completion |
| OpenEntryIfNeededAndContinue(); |
| } |
| |
| void AppCacheResponseReader::ContinueReadInfo() { |
| if (!entry_) { |
| ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); |
| return; |
| } |
| |
| int size = entry_->GetDataSize(kResponseInfoIndex); |
| if (size <= 0) { |
| ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); |
| return; |
| } |
| |
| buffer_ = new net::IOBuffer(size); |
| ReadRaw(kResponseInfoIndex, 0, buffer_.get(), size); |
| } |
| |
| void AppCacheResponseReader::ReadData(net::IOBuffer* buf, int buf_len, |
| net::CompletionCallback* callback) { |
| DCHECK(callback && !IsReadPending()); |
| DCHECK(buf && (buf_len >= 0)); |
| DCHECK(!buffer_.get() && !info_buffer_.get()); |
| |
| buffer_ = buf; |
| buffer_len_ = buf_len; |
| user_callback_ = callback; // cleared on completion |
| OpenEntryIfNeededAndContinue(); |
| } |
| |
| void AppCacheResponseReader::ContinueReadData() { |
| if (!entry_) { |
| ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); |
| return; |
| } |
| |
| if (read_position_ + buffer_len_ > range_length_) { |
| // TODO(michaeln): What about integer overflows? |
| DCHECK(range_length_ >= read_position_); |
| buffer_len_ = range_length_ - read_position_; |
| } |
| ReadRaw(kResponseContentIndex, range_offset_ + read_position_, |
| buffer_, buffer_len_); |
| } |
| |
| void AppCacheResponseReader::SetReadRange(int offset, int length) { |
| DCHECK(!IsReadPending() && !read_position_); |
| range_offset_ = offset; |
| range_length_ = length; |
| } |
| |
| void AppCacheResponseReader::OnIOComplete(int result) { |
| if (result >= 0) { |
| if (info_buffer_.get()) { |
| // Allocate and deserialize the http info structure. |
| Pickle pickle(buffer_->data(), result); |
| bool response_truncated = false; |
| info_buffer_->http_info.reset(new net::HttpResponseInfo); |
| info_buffer_->http_info->InitFromPickle(pickle, &response_truncated); |
| DCHECK(!response_truncated); |
| |
| // Also return the size of the response body |
| DCHECK(entry_); |
| info_buffer_->response_data_size = |
| entry_->GetDataSize(kResponseContentIndex); |
| } else { |
| read_position_ += result; |
| } |
| } |
| InvokeUserCompletionCallback(result); |
| } |
| |
| void AppCacheResponseReader::OpenEntryIfNeededAndContinue() { |
| int rv; |
| if (entry_) { |
| rv = net::OK; |
| } else if (!disk_cache_) { |
| rv = net::ERR_FAILED; |
| } else { |
| open_callback_ = new EntryCallback<AppCacheResponseReader>( |
| this, &AppCacheResponseReader::OnOpenEntryComplete); |
| rv = disk_cache_->OpenEntry(response_id_, &open_callback_->entry_ptr_, |
| open_callback_.get()); |
| } |
| |
| if (rv != net::ERR_IO_PENDING) |
| OnOpenEntryComplete(rv); |
| } |
| |
| void AppCacheResponseReader::OnOpenEntryComplete(int rv) { |
| DCHECK(info_buffer_.get() || buffer_.get()); |
| |
| if (open_callback_) { |
| if (rv == net::OK) { |
| entry_ = open_callback_->entry_ptr_; |
| open_callback_->entry_ptr_ = NULL; |
| } |
| open_callback_ = NULL; |
| } |
| |
| if (info_buffer_) |
| ContinueReadInfo(); |
| else |
| ContinueReadData(); |
| } |
| |
| // AppCacheResponseWriter ---------------------------------------------- |
| |
| AppCacheResponseWriter::AppCacheResponseWriter( |
| int64 response_id, AppCacheDiskCache* disk_cache) |
| : AppCacheResponseIO(response_id, disk_cache), |
| info_size_(0), write_position_(0), write_amount_(0), |
| creation_phase_(INITIAL_ATTEMPT) { |
| } |
| |
| AppCacheResponseWriter::~AppCacheResponseWriter() { |
| if (create_callback_) |
| create_callback_.release()->Cancel(); |
| } |
| |
| void AppCacheResponseWriter::WriteInfo(HttpResponseInfoIOBuffer* info_buf, |
| net::CompletionCallback* callback) { |
| DCHECK(callback && !IsWritePending()); |
| DCHECK(info_buf && info_buf->http_info.get()); |
| DCHECK(!buffer_.get() && !info_buffer_.get()); |
| |
| info_buffer_ = info_buf; |
| user_callback_ = callback; // cleared on completion |
| CreateEntryIfNeededAndContinue(); |
| } |
| |
| void AppCacheResponseWriter::ContinueWriteInfo() { |
| if (!entry_) { |
| ScheduleIOCompletionCallback(net::ERR_FAILED); |
| return; |
| } |
| |
| const bool kSkipTransientHeaders = true; |
| const bool kTruncated = false; |
| Pickle* pickle = new Pickle; |
| info_buffer_->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated); |
| write_amount_ = static_cast<int>(pickle->size()); |
| buffer_ = new WrappedPickleIOBuffer(pickle); // takes ownership of pickle |
| WriteRaw(kResponseInfoIndex, 0, buffer_, write_amount_); |
| } |
| |
| void AppCacheResponseWriter::WriteData(net::IOBuffer* buf, int buf_len, |
| net::CompletionCallback* callback) { |
| DCHECK(callback && !IsWritePending()); |
| DCHECK(buf && (buf_len >= 0)); |
| DCHECK(!buffer_.get() && !info_buffer_.get()); |
| |
| buffer_ = buf; |
| write_amount_ = buf_len; |
| user_callback_ = callback; // cleared on completion |
| CreateEntryIfNeededAndContinue(); |
| } |
| |
| void AppCacheResponseWriter::ContinueWriteData() { |
| if (!entry_) { |
| ScheduleIOCompletionCallback(net::ERR_FAILED); |
| return; |
| } |
| WriteRaw(kResponseContentIndex, write_position_, buffer_, write_amount_); |
| } |
| |
| void AppCacheResponseWriter::OnIOComplete(int result) { |
| if (result >= 0) { |
| DCHECK(write_amount_ == result); |
| if (!info_buffer_.get()) |
| write_position_ += result; |
| else |
| info_size_ = result; |
| } |
| InvokeUserCompletionCallback(result); |
| } |
| |
| void AppCacheResponseWriter::CreateEntryIfNeededAndContinue() { |
| int rv; |
| if (entry_) { |
| creation_phase_ = NO_ATTEMPT; |
| rv = net::OK; |
| } else if (!disk_cache_) { |
| creation_phase_ = NO_ATTEMPT; |
| rv = net::ERR_FAILED; |
| } else { |
| creation_phase_ = INITIAL_ATTEMPT; |
| create_callback_ = new EntryCallback<AppCacheResponseWriter>( |
| this, &AppCacheResponseWriter::OnCreateEntryComplete); |
| rv = disk_cache_->CreateEntry(response_id_, &create_callback_->entry_ptr_, |
| create_callback_.get()); |
| } |
| if (rv != net::ERR_IO_PENDING) |
| OnCreateEntryComplete(rv); |
| } |
| |
| void AppCacheResponseWriter::OnCreateEntryComplete(int rv) { |
| DCHECK(info_buffer_.get() || buffer_.get()); |
| |
| if (creation_phase_ == INITIAL_ATTEMPT) { |
| if (rv != net::OK) { |
| // We may try to overrite existing entries. |
| creation_phase_ = DOOM_EXISTING; |
| rv = disk_cache_->DoomEntry(response_id_, create_callback_.get()); |
| if (rv != net::ERR_IO_PENDING) |
| OnCreateEntryComplete(rv); |
| return; |
| } |
| } else if (creation_phase_ == DOOM_EXISTING) { |
| creation_phase_ = SECOND_ATTEMPT; |
| rv = disk_cache_->CreateEntry(response_id_, &create_callback_->entry_ptr_, |
| create_callback_.get()); |
| if (rv != net::ERR_IO_PENDING) |
| OnCreateEntryComplete(rv); |
| return; |
| } |
| |
| if (create_callback_) { |
| if (rv == net::OK) { |
| entry_ = create_callback_->entry_ptr_; |
| create_callback_->entry_ptr_ = NULL; |
| } |
| create_callback_ = NULL; |
| } |
| |
| if (info_buffer_) |
| ContinueWriteInfo(); |
| else |
| ContinueWriteData(); |
| } |
| |
| } // namespace appcache |
| |