mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
786 lines
22 KiB
C++
786 lines
22 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
#include "nsCache.h"
|
|
#include "nsDiskCache.h"
|
|
#include "nsDiskCacheDevice.h"
|
|
#include "nsDiskCacheStreams.h"
|
|
#include "nsCacheService.h"
|
|
#include "mozilla/FileUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
|
|
|
|
// we pick 16k as the max buffer size because that is the threshold above which
|
|
// we are unable to store the data in the cache block files
|
|
// see nsDiskCacheMap.[cpp,h]
|
|
#define kMaxBufferSize (16 * 1024)
|
|
|
|
// Assumptions:
|
|
// - cache descriptors live for life of streams
|
|
// - streams will only be used by FileTransport,
|
|
// they will not be directly accessible to clients
|
|
// - overlapped I/O is NOT supported
|
|
|
|
|
|
/******************************************************************************
|
|
* nsDiskCacheInputStream
|
|
*****************************************************************************/
|
|
class nsDiskCacheInputStream : public nsIInputStream {
|
|
|
|
public:
|
|
|
|
nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
|
|
PRFileDesc * fileDesc,
|
|
const char * buffer,
|
|
uint32_t endOfStream);
|
|
|
|
virtual ~nsDiskCacheInputStream();
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIINPUTSTREAM
|
|
|
|
private:
|
|
nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
|
|
PRFileDesc * mFD;
|
|
const char * mBuffer;
|
|
uint32_t mStreamEnd;
|
|
uint32_t mPos; // stream position
|
|
bool mClosed;
|
|
};
|
|
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheInputStream, nsIInputStream)
|
|
|
|
|
|
nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
|
|
PRFileDesc * fileDesc,
|
|
const char * buffer,
|
|
uint32_t endOfStream)
|
|
: mStreamIO(parent)
|
|
, mFD(fileDesc)
|
|
, mBuffer(buffer)
|
|
, mStreamEnd(endOfStream)
|
|
, mPos(0)
|
|
, mClosed(false)
|
|
{
|
|
NS_ADDREF(mStreamIO);
|
|
mStreamIO->IncrementInputStreamCount();
|
|
}
|
|
|
|
|
|
nsDiskCacheInputStream::~nsDiskCacheInputStream()
|
|
{
|
|
Close();
|
|
mStreamIO->DecrementInputStreamCount();
|
|
NS_RELEASE(mStreamIO);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheInputStream::Close()
|
|
{
|
|
if (!mClosed) {
|
|
if (mFD) {
|
|
(void) PR_Close(mFD);
|
|
mFD = nullptr;
|
|
}
|
|
mClosed = true;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheInputStream::Available(uint64_t * bytesAvailable)
|
|
{
|
|
if (mClosed) return NS_BASE_STREAM_CLOSED;
|
|
if (mStreamEnd < mPos) return NS_ERROR_UNEXPECTED;
|
|
|
|
*bytesAvailable = mStreamEnd - mPos;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheInputStream::Read(char * buffer, uint32_t count, uint32_t * bytesRead)
|
|
{
|
|
*bytesRead = 0;
|
|
|
|
if (mClosed) {
|
|
CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
|
|
"[stream=%p] stream was closed",
|
|
this, buffer, count));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mPos == mStreamEnd) {
|
|
CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
|
|
"[stream=%p] stream at end of file",
|
|
this, buffer, count));
|
|
return NS_OK;
|
|
}
|
|
if (mPos > mStreamEnd) {
|
|
CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
|
|
"[stream=%p] stream past end of file (!)",
|
|
this, buffer, count));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
if (count > mStreamEnd - mPos)
|
|
count = mStreamEnd - mPos;
|
|
|
|
if (mFD) {
|
|
// just read from file
|
|
int32_t result = PR_Read(mFD, buffer, count);
|
|
if (result < 0) {
|
|
nsresult rv = NS_ErrorAccordingToNSPR();
|
|
CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read PR_Read failed"
|
|
"[stream=%p, rv=%d, NSPR error %s",
|
|
this, int(rv), PR_ErrorToName(PR_GetError())));
|
|
return rv;
|
|
}
|
|
|
|
mPos += (uint32_t)result;
|
|
*bytesRead = (uint32_t)result;
|
|
|
|
} else if (mBuffer) {
|
|
// read data from mBuffer
|
|
memcpy(buffer, mBuffer + mPos, count);
|
|
mPos += count;
|
|
*bytesRead = count;
|
|
} else {
|
|
// no data source for input stream
|
|
}
|
|
|
|
CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
|
|
"[stream=%p, count=%ud, byteRead=%ud] ",
|
|
this, unsigned(count), unsigned(*bytesRead)));
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
|
|
void * closure,
|
|
uint32_t count,
|
|
uint32_t * bytesRead)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
|
|
{
|
|
*nonBlocking = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* nsDiskCacheOutputStream
|
|
*****************************************************************************/
|
|
class nsDiskCacheOutputStream : public nsIOutputStream
|
|
{
|
|
public:
|
|
nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
|
|
virtual ~nsDiskCacheOutputStream();
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOUTPUTSTREAM
|
|
|
|
void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
|
|
|
|
private:
|
|
nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
|
|
bool mClosed;
|
|
};
|
|
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheOutputStream,
|
|
nsIOutputStream)
|
|
|
|
nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
|
|
: mStreamIO(parent)
|
|
, mClosed(false)
|
|
{
|
|
NS_ADDREF(mStreamIO);
|
|
}
|
|
|
|
|
|
nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
|
|
{
|
|
Close();
|
|
ReleaseStreamIO();
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheOutputStream::Close()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
mozilla::TimeStamp start = mozilla::TimeStamp::Now();
|
|
|
|
if (!mClosed) {
|
|
mClosed = true;
|
|
// tell parent streamIO we are closing
|
|
rv = mStreamIO->CloseOutputStream(this);
|
|
}
|
|
|
|
mozilla::Telemetry::ID id;
|
|
if (NS_IsMainThread())
|
|
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD;
|
|
else
|
|
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
|
|
|
|
mozilla::Telemetry::AccumulateTimeDelta(id, start);
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheOutputStream::Flush()
|
|
{
|
|
if (mClosed) return NS_BASE_STREAM_CLOSED;
|
|
// yeah, yeah, well get to it...eventually...
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheOutputStream::Write(const char *buf, uint32_t count, uint32_t *bytesWritten)
|
|
{
|
|
if (mClosed) return NS_BASE_STREAM_CLOSED;
|
|
return mStreamIO->Write(buf, count, bytesWritten);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
|
|
{
|
|
NS_NOTREACHED("WriteFrom");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
|
|
void * closure,
|
|
uint32_t count,
|
|
uint32_t * bytesWritten)
|
|
{
|
|
NS_NOTREACHED("WriteSegments");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsDiskCacheOutputStream::IsNonBlocking(bool * nonBlocking)
|
|
{
|
|
*nonBlocking = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
* nsDiskCacheStreamIO
|
|
*****************************************************************************/
|
|
NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
|
|
|
|
nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
|
|
: mBinding(binding)
|
|
, mOutStream(nullptr)
|
|
, mInStreamCount(0)
|
|
, mFD(nullptr)
|
|
, mStreamEnd(0)
|
|
, mBufSize(0)
|
|
, mBuffer(nullptr)
|
|
{
|
|
mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
|
|
|
|
// acquire "death grip" on cache service
|
|
nsCacheService *service = nsCacheService::GlobalInstance();
|
|
NS_ADDREF(service);
|
|
}
|
|
|
|
|
|
nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
|
|
{
|
|
Close();
|
|
|
|
// release "death grip" on cache service
|
|
nsCacheService *service = nsCacheService::GlobalInstance();
|
|
NS_RELEASE(service);
|
|
}
|
|
|
|
|
|
void
|
|
nsDiskCacheStreamIO::Close()
|
|
{
|
|
// this should only be called from our destructor
|
|
// no one is interested in us anymore, so we don't need to grab any locks
|
|
|
|
// assert streams closed
|
|
NS_ASSERTION(!mOutStream, "output stream still open");
|
|
NS_ASSERTION(mInStreamCount == 0, "input stream still open");
|
|
NS_ASSERTION(!mFD, "file descriptor not closed");
|
|
|
|
DeleteBuffer();
|
|
}
|
|
|
|
|
|
// NOTE: called with service lock held
|
|
nsresult
|
|
nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(inputStream);
|
|
NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
|
|
|
|
*inputStream = nullptr;
|
|
|
|
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (mOutStream) {
|
|
NS_WARNING("already have an output stream open");
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult rv;
|
|
PRFileDesc * fd = nullptr;
|
|
|
|
mStreamEnd = mBinding->mCacheEntry->DataSize();
|
|
if (mStreamEnd == 0) {
|
|
// there's no data to read
|
|
NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size");
|
|
} else if (mBinding->mRecord.DataFile() == 0) {
|
|
// open file desc for data
|
|
rv = OpenCacheFile(PR_RDONLY, &fd);
|
|
if (NS_FAILED(rv)) return rv; // unable to open file
|
|
NS_ASSERTION(fd, "cache stream lacking open file.");
|
|
|
|
} else if (!mBuffer) {
|
|
// read block file for data
|
|
rv = ReadCacheBlocks(mStreamEnd);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
// else, mBuffer already contains all of the data (left over from a
|
|
// previous block-file read or write).
|
|
|
|
NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream");
|
|
|
|
// create a new input stream
|
|
nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd);
|
|
if (!inStream) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*inputStream = inStream);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// NOTE: called with service lock held
|
|
nsresult
|
|
nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(outputStream);
|
|
*outputStream = nullptr;
|
|
|
|
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
NS_ASSERTION(!mOutStream, "already have an output stream open");
|
|
NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
|
|
if (mOutStream || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mStreamEnd = mBinding->mCacheEntry->DataSize();
|
|
|
|
// Inits file or buffer and truncate at the desired offset
|
|
nsresult rv = SeekAndTruncate(offset);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// create a new output stream
|
|
mOutStream = new nsDiskCacheOutputStream(this);
|
|
if (!mOutStream) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*outputStream = mOutStream);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::ClearBinding()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
if (mBinding && mOutStream)
|
|
rv = Flush();
|
|
mBinding = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream * outputStream)
|
|
{
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); // grab service lock
|
|
|
|
if (outputStream != mOutStream) {
|
|
NS_WARNING("mismatched output streams");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// output stream is closing
|
|
if (!mBinding) { // if we're severed, just clear member variables
|
|
mOutStream = nullptr;
|
|
outputStream->ReleaseStreamIO();
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsresult rv = Flush();
|
|
if (NS_FAILED(rv))
|
|
NS_WARNING("Flush() failed");
|
|
|
|
mOutStream = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::Flush()
|
|
{
|
|
NS_ASSERTION(mBinding, "oops");
|
|
|
|
CACHE_LOG_DEBUG(("CACHE: Flush [%x doomed=%u]\n",
|
|
mBinding->mRecord.HashNumber(), mBinding->mDoomed));
|
|
|
|
// When writing to a file, just close the file
|
|
if (mFD) {
|
|
(void) PR_Close(mFD);
|
|
mFD = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
// write data to cache blocks, or flush mBuffer to file
|
|
nsDiskCacheMap *cacheMap = mDevice->CacheMap(); // get map reference
|
|
nsresult rv;
|
|
|
|
bool written = false;
|
|
|
|
if (mStreamEnd <= kMaxBufferSize) {
|
|
// store data (if any) in cache block files
|
|
|
|
// delete existing storage
|
|
nsDiskCacheRecord * record = &mBinding->mRecord;
|
|
if (record->DataLocationInitialized()) {
|
|
rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("cacheMap->DeleteStorage() failed.");
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// flush buffer to block files
|
|
written = true;
|
|
if (mStreamEnd > 0) {
|
|
rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WriteDataCacheBlocks() failed.");
|
|
written = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!written) {
|
|
// failed to store in cacheblocks, save as separate file
|
|
rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary
|
|
|
|
if (mFD) {
|
|
// Update the file size of the disk file in the cache
|
|
UpdateFileSize();
|
|
|
|
// close file descriptor
|
|
(void) PR_Close(mFD);
|
|
mFD = nullptr;
|
|
}
|
|
else
|
|
NS_WARNING("no file descriptor");
|
|
|
|
// close mFD first if possible before returning if FlushBufferToFile
|
|
// failed
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// XXX do we need this here? WriteDataCacheBlocks() calls UpdateRecord()
|
|
// update cache map if entry isn't doomed
|
|
if (!mBinding->mDoomed) {
|
|
rv = cacheMap->UpdateRecord(&mBinding->mRecord);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("cacheMap->UpdateRecord() failed.");
|
|
return rv; // XXX doom cache entry
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// assumptions:
|
|
// only one thread writing at a time
|
|
// never have both output and input streams open
|
|
// OnDataSizeChanged() will have already been called to update entry->DataSize()
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::Write( const char * buffer,
|
|
uint32_t count,
|
|
uint32_t * bytesWritten)
|
|
{
|
|
// grab service lock
|
|
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_WRITE));
|
|
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (mInStreamCount) {
|
|
// we have open input streams already
|
|
// this is an error until we support overlapped I/O
|
|
NS_WARNING("Attempting to write to cache entry with open input streams.\n");
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_ASSERTION(count, "Write called with count of zero");
|
|
|
|
// Not writing to file, and it will fit in the cachedatablocks?
|
|
if (!mFD && (mStreamEnd + count <= kMaxBufferSize)) {
|
|
|
|
// We have more data than the current buffer size?
|
|
if ((mStreamEnd + count > mBufSize) && (mBufSize < kMaxBufferSize)) {
|
|
// Try to increase buffer to the max size, no problem if not
|
|
// succesful, we just use what is available.
|
|
char *newbuf = (char *) realloc(mBuffer, kMaxBufferSize);
|
|
if (newbuf) {
|
|
// Use the new larger buffer
|
|
mBuffer = newbuf;
|
|
mBufSize = kMaxBufferSize;
|
|
}
|
|
}
|
|
|
|
// Store in the buffer but only if it fits
|
|
if ((count > 0) && (mStreamEnd + count <= mBufSize)) {
|
|
memcpy(mBuffer + mStreamEnd, buffer, count);
|
|
mStreamEnd += count;
|
|
*bytesWritten = count;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// There are more bytes than fit in the buffer/cacheblocks, switch to file
|
|
if (!mFD) {
|
|
// Opens a cache file and write the buffer to it
|
|
nsresult rv = FlushBufferToFile();
|
|
if (NS_FAILED(rv)) {
|
|
*bytesWritten = 0;
|
|
return rv;
|
|
}
|
|
}
|
|
// Write directly to the file
|
|
if (PR_Write(mFD, buffer, count) != (int32_t)count) {
|
|
NS_WARNING("failed to write all data");
|
|
*bytesWritten = 0;
|
|
return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
|
|
}
|
|
mStreamEnd += count;
|
|
*bytesWritten = count;
|
|
|
|
UpdateFileSize();
|
|
NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nsDiskCacheStreamIO::UpdateFileSize()
|
|
{
|
|
NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
|
|
|
|
nsDiskCacheRecord * record = &mBinding->mRecord;
|
|
const uint32_t oldSizeK = record->DataFileSize();
|
|
uint32_t newSizeK = (mStreamEnd + 0x03FF) >> 10;
|
|
|
|
// make sure the size won't overflow (bug #651100)
|
|
if (newSizeK > kMaxDataSizeK)
|
|
newSizeK = kMaxDataSizeK;
|
|
|
|
if (newSizeK == oldSizeK) return;
|
|
|
|
record->SetDataFileSize(newSizeK);
|
|
|
|
// update cache size totals
|
|
nsDiskCacheMap * cacheMap = mDevice->CacheMap();
|
|
cacheMap->DecrementTotalSize(oldSizeK); // decrement old size
|
|
cacheMap->IncrementTotalSize(newSizeK); // increment new size
|
|
|
|
if (!mBinding->mDoomed) {
|
|
nsresult rv = cacheMap->UpdateRecord(record);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("cacheMap->UpdateRecord() failed.");
|
|
// XXX doom cache entry?
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::OpenCacheFile(int flags, PRFileDesc ** fd)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(fd);
|
|
|
|
CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile"));
|
|
|
|
nsresult rv;
|
|
nsDiskCacheMap * cacheMap = mDevice->CacheMap();
|
|
nsCOMPtr<nsIFile> localFile;
|
|
|
|
rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
|
|
nsDiskCache::kData,
|
|
!!(flags & PR_CREATE_FILE),
|
|
getter_AddRefs(localFile));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// create PRFileDesc for input stream - the 00600 is just for consistency
|
|
return localFile->OpenNSPRFileDesc(flags, 00600, fd);
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::ReadCacheBlocks(uint32_t bufferSize)
|
|
{
|
|
NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream");
|
|
NS_ASSERTION(bufferSize <= kMaxBufferSize, "bufferSize too large for buffer");
|
|
NS_ASSERTION(mStreamEnd <= bufferSize, "data too large for buffer");
|
|
|
|
nsDiskCacheRecord * record = &mBinding->mRecord;
|
|
if (!record->DataLocationInitialized()) return NS_OK;
|
|
|
|
NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file");
|
|
|
|
if (!mBuffer) {
|
|
// allocate buffer
|
|
mBuffer = (char *) malloc(bufferSize);
|
|
if (!mBuffer) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
mBufSize = bufferSize;
|
|
}
|
|
|
|
// read data stored in cache block files
|
|
nsDiskCacheMap *map = mDevice->CacheMap(); // get map reference
|
|
return map->ReadDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::FlushBufferToFile()
|
|
{
|
|
nsresult rv;
|
|
nsDiskCacheRecord * record = &mBinding->mRecord;
|
|
|
|
if (!mFD) {
|
|
if (record->DataLocationInitialized() && (record->DataFile() > 0)) {
|
|
// remove cache block storage
|
|
nsDiskCacheMap * cacheMap = mDevice->CacheMap();
|
|
rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
record->SetDataFileGeneration(mBinding->mGeneration);
|
|
|
|
// allocate file
|
|
rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
|
|
if (dataSize != -1)
|
|
mozilla::fallocate(mFD, NS_MIN<int64_t>(dataSize, kPreallocateLimit));
|
|
}
|
|
|
|
// write buffer to the file
|
|
if (PR_Write(mFD, mBuffer, mStreamEnd) != (int32_t)mStreamEnd) {
|
|
NS_WARNING("failed to flush all data");
|
|
return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
|
|
}
|
|
|
|
// buffer is no longer valid
|
|
DeleteBuffer();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nsDiskCacheStreamIO::DeleteBuffer()
|
|
{
|
|
if (mBuffer) {
|
|
free(mBuffer);
|
|
mBuffer = nullptr;
|
|
mBufSize = 0;
|
|
}
|
|
}
|
|
|
|
size_t
|
|
nsDiskCacheStreamIO::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf)
|
|
{
|
|
size_t usage = aMallocSizeOf(this);
|
|
|
|
usage += aMallocSizeOf(mFD);
|
|
usage += aMallocSizeOf(mBuffer);
|
|
|
|
return usage;
|
|
}
|
|
|
|
nsresult
|
|
nsDiskCacheStreamIO::SeekAndTruncate(uint32_t offset)
|
|
{
|
|
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE;
|
|
|
|
// Set the current end to the desired offset
|
|
mStreamEnd = offset;
|
|
|
|
// Currently stored in file?
|
|
if (mBinding->mRecord.DataLocationInitialized() &&
|
|
(mBinding->mRecord.DataFile() == 0)) {
|
|
if (!mFD) {
|
|
// we need an mFD, we better open it now
|
|
nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
if (offset) {
|
|
if (PR_Seek(mFD, offset, PR_SEEK_SET) == -1)
|
|
return NS_ErrorAccordingToNSPR();
|
|
}
|
|
nsDiskCache::Truncate(mFD, offset);
|
|
UpdateFileSize();
|
|
|
|
// When we starting at zero again, close file and start with buffer.
|
|
// If offset is non-zero (and within buffer) an option would be
|
|
// to read the file into the buffer, but chance is high that it is
|
|
// rewritten to the file anyway.
|
|
if (offset == 0) {
|
|
// close file descriptor
|
|
(void) PR_Close(mFD);
|
|
mFD = nullptr;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// read data into mBuffer if not read yet.
|
|
if (offset && !mBuffer) {
|
|
nsresult rv = ReadCacheBlocks(kMaxBufferSize);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// stream buffer sanity check
|
|
NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "bad stream");
|
|
return NS_OK;
|
|
}
|