blob: 6710e498c9235b6508eef02bd6a67a9f254c94f3 [file] [log] [blame]
/*
* 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