| /* |
| * Copyright 2011, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "CacheResult.h" |
| |
| #include "WebResponse.h" |
| #include "WebUrlLoaderClient.h" |
| #include <platform/FileSystem.h> |
| #include <wtf/text/CString.h> |
| |
| using namespace base; |
| using namespace disk_cache; |
| using namespace net; |
| using namespace std; |
| |
| namespace android { |
| |
| // All public methods are called on a UI thread but we do work on the |
| // Chromium thread. However, because we block the WebCore thread while this |
| // work completes, we can never receive new public method calls while the |
| // Chromium thread work is in progress. |
| |
| // Copied from HttpCache |
| enum { |
| kResponseInfoIndex = 0, |
| kResponseContentIndex |
| }; |
| |
| CacheResult::CacheResult(disk_cache::Entry* entry, String url) |
| : m_entry(entry) |
| , m_onResponseHeadersDoneCallback(this, &CacheResult::onResponseHeadersDone) |
| , m_onReadNextChunkDoneCallback(this, &CacheResult::onReadNextChunkDone) |
| , m_url(url) |
| { |
| ASSERT(m_entry); |
| } |
| |
| CacheResult::~CacheResult() |
| { |
| m_entry->Close(); |
| // TODO: Should we also call DoneReadingFromEntry() on the cache for our |
| // entry? |
| } |
| |
| int64 CacheResult::contentSize() const |
| { |
| // The android stack does not take the content length from the HTTP response |
| // headers but calculates it when writing the content to disk. It can never |
| // overflow a long because we limit the cache size. |
| return m_entry->GetDataSize(kResponseContentIndex); |
| } |
| |
| bool CacheResult::firstResponseHeader(const char* name, String* result, bool allowEmptyString) const |
| { |
| string value; |
| if (responseHeaders() && responseHeaders()->EnumerateHeader(NULL, name, &value) && (!value.empty() || allowEmptyString)) { |
| *result = String(value.c_str()); |
| return true; |
| } |
| return false; |
| } |
| |
| String CacheResult::mimeType() const |
| { |
| string mimeType; |
| if (responseHeaders()) |
| responseHeaders()->GetMimeType(&mimeType); |
| if (!mimeType.length() && m_url.length()) |
| mimeType = WebResponse::resolveMimeType(std::string(m_url.utf8().data(), m_url.length()), ""); |
| return String(mimeType.c_str()); |
| } |
| |
| int64 CacheResult::expires() const |
| { |
| // We have to do this manually, rather than using HttpResponseHeaders::GetExpiresValue(), |
| // to handle the "-1" and "0" special cases. |
| string expiresString; |
| if (responseHeaders() && responseHeaders()->EnumerateHeader(NULL, "expires", &expiresString)) { |
| wstring expiresStringWide(expiresString.begin(), expiresString.end()); // inflate ascii |
| // We require the time expressed as ms since the epoch. |
| Time time; |
| if (Time::FromString(expiresStringWide.c_str(), &time)) { |
| // Will not overflow for a very long time! |
| return static_cast<int64>(1000.0 * time.ToDoubleT()); |
| } |
| |
| if (expiresString == "-1" || expiresString == "0") |
| return 0; |
| } |
| |
| // TODO |
| // The Android stack applies a heuristic to set an expiry date if the |
| // expires header is not set or can't be parsed. I'm not sure whether the Chromium cache |
| // does this, and if so, it may not be possible for us to get hold of it |
| // anyway to set it on the result. |
| return -1; |
| } |
| |
| int CacheResult::responseCode() const |
| { |
| return responseHeaders() ? responseHeaders()->response_code() : 0; |
| } |
| |
| bool CacheResult::writeToFile(const String& filePath) const |
| { |
| // Getting the headers is potentially async, so post to the Chromium thread |
| // and block here. |
| MutexLocker lock(m_mutex); |
| |
| base::Thread* thread = WebUrlLoaderClient::ioThread(); |
| if (!thread) |
| return false; |
| |
| m_filePath = filePath.threadsafeCopy(); |
| m_isAsyncOperationInProgress = true; |
| |
| thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(const_cast<CacheResult*>(this), &CacheResult::writeToFileImpl)); |
| |
| while (m_isAsyncOperationInProgress) |
| m_condition.wait(m_mutex); |
| |
| return m_wasWriteToFileSuccessful; |
| } |
| |
| void CacheResult::writeToFileImpl() |
| { |
| m_bufferSize = m_entry->GetDataSize(kResponseContentIndex); |
| m_readOffset = 0; |
| m_wasWriteToFileSuccessful = false; |
| readNextChunk(); |
| } |
| |
| void CacheResult::readNextChunk() |
| { |
| m_buffer = new IOBuffer(m_bufferSize); |
| int rv = m_entry->ReadData(kResponseInfoIndex, m_readOffset, m_buffer, m_bufferSize, &m_onReadNextChunkDoneCallback); |
| if (rv == ERR_IO_PENDING) |
| return; |
| |
| onReadNextChunkDone(rv); |
| }; |
| |
| void CacheResult::onReadNextChunkDone(int size) |
| { |
| if (size > 0) { |
| // Still more reading to be done. |
| if (writeChunkToFile()) { |
| // TODO: I assume that we need to clear and resize the buffer for the next read? |
| m_readOffset += size; |
| m_bufferSize -= size; |
| readNextChunk(); |
| } else |
| onWriteToFileDone(); |
| return; |
| } |
| |
| if (!size) { |
| // Reached end of file. |
| if (writeChunkToFile()) |
| m_wasWriteToFileSuccessful = true; |
| } |
| onWriteToFileDone(); |
| } |
| |
| bool CacheResult::writeChunkToFile() |
| { |
| PlatformFileHandle file; |
| file = openFile(m_filePath, OpenForWrite); |
| if (!isHandleValid(file)) |
| return false; |
| return WebCore::writeToFile(file, m_buffer->data(), m_bufferSize) == m_bufferSize; |
| } |
| |
| void CacheResult::onWriteToFileDone() |
| { |
| MutexLocker lock(m_mutex); |
| m_isAsyncOperationInProgress = false; |
| m_condition.signal(); |
| } |
| |
| HttpResponseHeaders* CacheResult::responseHeaders() const |
| { |
| MutexLocker lock(m_mutex); |
| if (m_responseHeaders) |
| return m_responseHeaders; |
| |
| // Getting the headers is potentially async, so post to the Chromium thread |
| // and block here. |
| base::Thread* thread = WebUrlLoaderClient::ioThread(); |
| if (!thread) |
| return 0; |
| |
| m_isAsyncOperationInProgress = true; |
| thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(const_cast<CacheResult*>(this), &CacheResult::responseHeadersImpl)); |
| |
| while (m_isAsyncOperationInProgress) |
| m_condition.wait(m_mutex); |
| |
| return m_responseHeaders; |
| } |
| |
| void CacheResult::responseHeadersImpl() |
| { |
| m_bufferSize = m_entry->GetDataSize(kResponseInfoIndex); |
| m_buffer = new IOBuffer(m_bufferSize); |
| |
| int rv = m_entry->ReadData(kResponseInfoIndex, 0, m_buffer, m_bufferSize, &m_onResponseHeadersDoneCallback); |
| if (rv == ERR_IO_PENDING) |
| return; |
| |
| onResponseHeadersDone(rv); |
| }; |
| |
| void CacheResult::onResponseHeadersDone(int size) |
| { |
| MutexLocker lock(m_mutex); |
| // It's OK to throw away the HttpResponseInfo object as we hold our own ref |
| // to the headers. |
| HttpResponseInfo response; |
| bool truncated = false; // TODO: Waht is this param for? |
| if (size == m_bufferSize && HttpCache::ParseResponseInfo(m_buffer->data(), m_bufferSize, &response, &truncated)) |
| m_responseHeaders = response.headers; |
| m_isAsyncOperationInProgress = false; |
| m_condition.signal(); |
| } |
| |
| } // namespace android |