Renamed all Delete methods to Remove. --pete git-svn-id: svn://10.0.0.236/trunk@99803 18797224-902f-48f8-a5cc-f745e15eee43
853 lines
26 KiB
C++
853 lines
26 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is nsDiskCacheMap.cpp, released March 23, 2001.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape Communications
|
|
* Corporation. Portions created by Netscape are
|
|
* Copyright (C) 2001 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Patrick C. Beard <beard@netscape.com>
|
|
* Gordon Sheridan <gordon@netscape.com>
|
|
*/
|
|
|
|
#include "nsDiskCacheMap.h"
|
|
#include "nsDiskCacheBinding.h"
|
|
#include "nsDiskCacheEntry.h"
|
|
|
|
#include "nsCache.h"
|
|
|
|
#include "nsCRT.h"
|
|
|
|
#include <string.h>
|
|
|
|
|
|
/******************************************************************************
|
|
* nsDiskCacheBucket
|
|
*****************************************************************************/
|
|
|
|
void
|
|
nsDiskCacheBucket::Swap()
|
|
{
|
|
nsDiskCacheRecord * record = &mRecords[0];
|
|
for (int i = 0; i < kRecordsPerBucket; ++i) {
|
|
if (record->HashNumber() == 0)
|
|
break;
|
|
record->Swap();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsDiskCacheBucket::Unswap()
|
|
{
|
|
nsDiskCacheRecord * record = &mRecords[0];
|
|
for (int i = 0; i < kRecordsPerBucket; ++i) {
|
|
if (record->HashNumber() == 0)
|
|
break;
|
|
record->Unswap();
|
|
}
|
|
}
|
|
|
|
|
|
PRUint32
|
|
nsDiskCacheBucket::CountRecords()
|
|
{
|
|
if (mRecords[0].HashNumber() == 0) return 0;
|
|
|
|
PRUint32 i = kRecordsPerBucket >> 1;
|
|
PRUint32 offset = kRecordsPerBucket >> 2;
|
|
|
|
while (offset > 0) {
|
|
if (mRecords[i].HashNumber()) i += offset;
|
|
else i -= offset;
|
|
offset >>= 1;
|
|
}
|
|
|
|
if (mRecords[i].HashNumber() != 0)
|
|
++i;
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
PRUint32
|
|
nsDiskCacheBucket::EvictionRank(PRUint32 targetRank)
|
|
{
|
|
PRUint32 rank = 0;
|
|
for (int i = CountRecords() - 1; i >= 0; --i) {
|
|
if ((rank < mRecords[i].EvictionRank()) &&
|
|
((targetRank == 0) || (mRecords[i].EvictionRank() < targetRank)))
|
|
rank = mRecords[i].EvictionRank();
|
|
}
|
|
return rank;
|
|
}
|
|
|
|
|
|
PRInt32
|
|
nsDiskCacheBucket::VisitEachRecord(nsDiskCacheRecordVisitor * visitor,
|
|
PRUint32 evictionRank,
|
|
PRUint32 * result)
|
|
{
|
|
PRUint32 recordsDeleted = 0;
|
|
PRInt32 rv = kVisitNextRecord;
|
|
PRInt32 last = CountRecords() - 1;
|
|
|
|
// call visitor for each entry (matching any eviction rank)
|
|
for (int i = last; i >= 0; i--) {
|
|
if (evictionRank > mRecords[i].EvictionRank()) continue;
|
|
|
|
rv = visitor->VisitRecord(&mRecords[i]);
|
|
if (rv == kVisitNextRecord) continue;
|
|
|
|
if (rv == kDeleteRecordAndContinue) {
|
|
mRecords[i] = mRecords[last];
|
|
mRecords[last].SetHashNumber(0);
|
|
--last;
|
|
++recordsDeleted;
|
|
continue;
|
|
}
|
|
|
|
*result = recordsDeleted;
|
|
return kStopVisitingRecords; // rv == kStopVisitingRecords
|
|
}
|
|
|
|
*result = recordsDeleted;
|
|
return rv;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* nsDiskCacheMap
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
* File operations
|
|
*/
|
|
|
|
nsresult
|
|
nsDiskCacheMap::Open(nsILocalFile * cacheDirectory)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(cacheDirectory);
|
|
if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
|
|
|
|
mCacheDirectory = cacheDirectory; // save a reference for ourselves
|
|
|
|
// create nsILocalFile for _CACHE_MAP_
|
|
nsresult rv;
|
|
nsCOMPtr<nsIFile> file;
|
|
rv = cacheDirectory->Clone(getter_AddRefs(file));
|
|
nsCOMPtr<nsILocalFile> localFile(do_QueryInterface(file, &rv));
|
|
if (NS_FAILED(rv)) return rv;
|
|
rv = localFile->Append("_CACHE_MAP_");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// open the file
|
|
rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00666, &mMapFD);
|
|
if (NS_FAILED(rv)) return rv; // unable to open or create file
|
|
|
|
// check size of map file
|
|
PRUint32 mapSize = PR_Available(mMapFD);
|
|
if (mapSize < 0) {
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
goto error_exit;
|
|
}
|
|
|
|
if (mapSize == 0) {
|
|
// create the file - initialize in memory
|
|
mHeader.mVersion = nsDiskCache::kCurrentVersion;
|
|
mHeader.mDataSize = 0;
|
|
mHeader.mEntryCount = 0;
|
|
mHeader.mIsDirty = PR_TRUE;
|
|
for (int i = 0; i < kBucketsPerTable; ++i) {
|
|
mHeader.mEvictionRank[i] = 0;
|
|
}
|
|
|
|
nsCRT::zero(mHeader.reserved, nsDiskCacheHeader::kReservedBytes);
|
|
nsCRT::zero(mBuckets, sizeof(nsDiskCacheBucket) * kBucketsPerTable);
|
|
|
|
} else if (mapSize == kCacheMapSize) {
|
|
// read it in
|
|
PRUint32 bytesRead = PR_Read(mMapFD, &mHeader, kCacheMapSize);
|
|
if (kCacheMapSize != bytesRead) {
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
goto error_exit;
|
|
}
|
|
mHeader.Unswap();
|
|
if (mHeader.mIsDirty || mHeader.mVersion != nsDiskCache::kCurrentVersion) {
|
|
rv = NS_ERROR_FILE_CORRUPTED;
|
|
goto error_exit;
|
|
}
|
|
|
|
// Unswap each bucket
|
|
for (PRUint32 i = 0; i < kBucketsPerTable; ++i) {
|
|
mBuckets[i].Unswap();
|
|
}
|
|
|
|
// XXX verify entry count, check size(?)
|
|
|
|
} else {
|
|
rv = NS_ERROR_FILE_CORRUPTED;
|
|
goto error_exit;
|
|
}
|
|
|
|
rv = OpenBlockFiles();
|
|
if (NS_FAILED(rv)) goto error_exit;
|
|
|
|
// set dirty bit and flush header
|
|
mHeader.mIsDirty = PR_TRUE;
|
|
rv = FlushHeader();
|
|
if (NS_FAILED(rv)) goto error_exit;
|
|
|
|
return NS_OK;
|
|
|
|
error_exit:
|
|
(void) CloseBlockFiles();
|
|
|
|
if (mMapFD) {
|
|
(void) PR_Close(mMapFD);
|
|
mMapFD = nsnull;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::Close()
|
|
{
|
|
if (!mMapFD) return NS_OK;
|
|
|
|
// close block files
|
|
nsresult rv = CloseBlockFiles();
|
|
if (NS_FAILED(rv)) goto exit; // this is going to be a mess...
|
|
|
|
// write map record buckets
|
|
rv = FlushBuckets(PR_FALSE); // don't bother swapping buckets back
|
|
if (NS_FAILED(rv)) goto exit;
|
|
|
|
// clear dirty bit
|
|
mHeader.mIsDirty = PR_FALSE;
|
|
|
|
rv = FlushHeader();
|
|
|
|
exit:
|
|
PRStatus err = PR_Close(mMapFD);
|
|
mMapFD = nsnull;
|
|
|
|
if (NS_FAILED(rv)) return rv;
|
|
return err == PR_SUCCESS ? NS_OK : NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::FlushHeader()
|
|
{
|
|
if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// seek to beginning of cache map
|
|
PRInt32 filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
|
|
if (filePos != 0) return NS_ERROR_UNEXPECTED;
|
|
|
|
// write the header
|
|
mHeader.Swap();
|
|
PRInt32 bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
|
|
mHeader.Unswap();
|
|
if (sizeof(nsDiskCacheHeader) != bytesWritten) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::FlushBuckets(PRBool unswap)
|
|
{
|
|
if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// seek to beginning of buckets
|
|
PRInt32 filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
|
|
if (filePos != sizeof(nsDiskCacheHeader)) return NS_ERROR_UNEXPECTED;
|
|
|
|
// Swap each bucket
|
|
for (PRUint32 i = 0; i < kBucketsPerTable; ++i) {
|
|
mBuckets[i].Swap();
|
|
}
|
|
|
|
PRInt32 bytesWritten = PR_Write(mMapFD, &mBuckets, sizeof(nsDiskCacheBucket) * kBucketsPerTable);
|
|
|
|
if (unswap) {
|
|
// Unswap each bucket
|
|
for (PRUint32 i = 0; i < kBucketsPerTable; ++i) {
|
|
mBuckets[i].Unswap();
|
|
}
|
|
}
|
|
|
|
if ( sizeof(nsDiskCacheBucket) * kBucketsPerTable != bytesWritten) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Record operations
|
|
*/
|
|
|
|
nsresult
|
|
nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
|
|
nsDiskCacheRecord * oldRecord)
|
|
{
|
|
nsresult rv;
|
|
PRUint32 hashNumber = mapRecord->HashNumber();
|
|
nsDiskCacheBucket * bucket;
|
|
PRUint32 bucketIndex = GetBucketIndex(hashNumber);
|
|
int i;
|
|
|
|
oldRecord->SetHashNumber(0); // signify no record
|
|
|
|
rv = GetBucketForHashNumber(hashNumber, &bucket);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsDiskCacheRecord * mostEvictable = &bucket->mRecords[0];
|
|
for (i = 0; i < kRecordsPerBucket; ++i) {
|
|
if (bucket->mRecords[i].HashNumber() == 0) {
|
|
// stick the new record here
|
|
bucket->mRecords[i] = *mapRecord;
|
|
++mHeader.mEntryCount;
|
|
|
|
// update eviction rank in header if necessary
|
|
if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
|
|
mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
|
|
|
|
NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(0),
|
|
"eviction rank out of sync");
|
|
return NS_OK;
|
|
}
|
|
|
|
if (bucket->mRecords[i].EvictionRank() > mostEvictable->EvictionRank())
|
|
mostEvictable = &bucket->mRecords[i];
|
|
}
|
|
|
|
*oldRecord = *mostEvictable; // i == kRecordsPerBucket, so evict the mostEvictable
|
|
*mostEvictable = *mapRecord; // replace it with the new record
|
|
|
|
// check if we need to update mostEvictable entry in header
|
|
if ((oldRecord->HashNumber() != 0) ||
|
|
(mapRecord->EvictionRank() > mHeader.mEvictionRank[bucketIndex])) {
|
|
|
|
mHeader.mEvictionRank[bucketIndex] = bucket->EvictionRank(0);
|
|
}
|
|
|
|
NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(0),
|
|
"eviction rank out of sync");
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
|
|
{
|
|
PRUint32 hashNumber = mapRecord->HashNumber();
|
|
nsDiskCacheBucket * bucket;
|
|
nsresult rv = GetBucketForHashNumber(hashNumber, &bucket);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
for (int i = 0; i < kRecordsPerBucket; ++i) {
|
|
if (bucket->mRecords[i].HashNumber() == mapRecord->HashNumber()) {
|
|
PRUint32 oldRank = bucket->mRecords[i].EvictionRank();
|
|
|
|
// stick the new record here
|
|
bucket->mRecords[i] = *mapRecord;
|
|
|
|
// update eviction rank in header if necessary
|
|
PRUint32 bucketIndex = GetBucketIndex(mapRecord->HashNumber());
|
|
if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
|
|
mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
|
|
else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
|
|
mHeader.mEvictionRank[bucketIndex] = bucket->EvictionRank(0);
|
|
|
|
NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(0),
|
|
"eviction rank out of sync");
|
|
return NS_OK;
|
|
}
|
|
}
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::FindRecord( PRUint32 hashNumber, nsDiskCacheRecord * result)
|
|
{
|
|
nsDiskCacheBucket * bucket;
|
|
nsresult rv = GetBucketForHashNumber(hashNumber, &bucket);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
for (int i = 0; i < kRecordsPerBucket; ++i) {
|
|
if (bucket->mRecords[i].HashNumber() == 0) break;
|
|
|
|
if (bucket->mRecords[i].HashNumber() == hashNumber) {
|
|
*result = bucket->mRecords[i]; // copy the record
|
|
NS_ASSERTION(result->ValidRecord(), "bad cache map record");
|
|
return NS_OK;
|
|
}
|
|
}
|
|
return NS_ERROR_CACHE_KEY_NOT_FOUND;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
|
|
{
|
|
nsDiskCacheBucket * bucket;
|
|
nsresult rv = GetBucketForHashNumber(mapRecord->HashNumber(), &bucket);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRUint32 count = bucket->CountRecords();
|
|
for (PRUint32 i = 0; i < count; ++i) {
|
|
if (bucket->mRecords[i].HashNumber() == mapRecord->HashNumber()) {
|
|
// found it, now delete it.
|
|
PRUint32 evictionRank = bucket->mRecords[i].EvictionRank();
|
|
NS_ASSERTION(evictionRank == mapRecord->EvictionRank(), "evictionRank out of sync");
|
|
if (i != (count - 1)) { // if not the last record, shift last record into opening
|
|
bucket->mRecords[i] = bucket->mRecords[count - 1];
|
|
}
|
|
bucket->mRecords[count - 1].SetHashNumber(0); // clear last record
|
|
mHeader.mEntryCount--;
|
|
// update eviction rank
|
|
PRUint32 bucketIndex = GetBucketIndex(mapRecord->HashNumber());
|
|
if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
|
|
mHeader.mEvictionRank[bucketIndex] = bucket->EvictionRank(0);
|
|
}
|
|
|
|
NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == bucket->EvictionRank(0),
|
|
"eviction rank out of sync");
|
|
return NS_OK;
|
|
}
|
|
}
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
|
|
/**
|
|
* VisitRecords
|
|
*
|
|
* Visit every record in cache map in the most convenient order
|
|
*/
|
|
nsresult
|
|
nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
|
|
{
|
|
for (PRUint32 i = 0; i < kBucketsPerTable; ++i) {
|
|
// get bucket
|
|
PRUint32 recordsDeleted;
|
|
PRBool continueFlag = mBuckets[i].VisitEachRecord(visitor, 0, &recordsDeleted);
|
|
if (recordsDeleted) {
|
|
// recalc eviction rank
|
|
mHeader.mEvictionRank[i] = mBuckets[i].EvictionRank(0);
|
|
mHeader.mEntryCount -= recordsDeleted;
|
|
// XXX write bucket
|
|
}
|
|
NS_ASSERTION(mHeader.mEvictionRank[i] == mBuckets[i].EvictionRank(0),
|
|
"eviction rank out of sync");
|
|
if (!continueFlag) break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* EvictRecords
|
|
*
|
|
* Just like VisitRecords, but visits the records in order of their eviction rank
|
|
*/
|
|
nsresult
|
|
nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
|
|
{
|
|
PRUint32 tempRank[kBucketsPerTable];
|
|
int i;
|
|
|
|
// copy eviction rank array
|
|
for (i = 0; i < kBucketsPerTable; ++i)
|
|
tempRank[i] = mHeader.mEvictionRank[i];
|
|
|
|
while (1) {
|
|
|
|
// find bucket with highest eviction rank
|
|
PRUint32 rank = 0;
|
|
PRUint32 index = 0;
|
|
for (i = 0; i < kBucketsPerTable; ++i) {
|
|
if (rank < tempRank[i]) {
|
|
rank = tempRank[i];
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
if (rank == 0) break; // we've examined all the records
|
|
NS_ASSERTION(mHeader.mEvictionRank[index] == mBuckets[index].EvictionRank(0),
|
|
"header eviction rank out of sync");
|
|
|
|
// visit records in bucket with eviction ranks >= target eviction rank
|
|
PRUint32 recordsDeleted;
|
|
PRInt32 continueResult = mBuckets[index].VisitEachRecord(visitor, rank, &recordsDeleted);
|
|
if (recordsDeleted) {
|
|
// recalc eviction rank
|
|
mHeader.mEvictionRank[index] = mBuckets[index].EvictionRank(0);
|
|
mHeader.mEntryCount -= recordsDeleted;
|
|
// XXX write bucket
|
|
}
|
|
if (continueResult == kStopVisitingRecords) break;
|
|
|
|
// find greatest rank less than 'rank'
|
|
tempRank[index] = mBuckets[index].EvictionRank(rank);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::OpenBlockFiles()
|
|
{
|
|
// create nsILocalFile for block file
|
|
nsCOMPtr<nsILocalFile> blockFile;
|
|
nsresult rv;
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
|
|
if (NS_FAILED(rv)) goto error_exit;
|
|
|
|
PRUint32 blockSize = GetBlockSizeForIndex(i);
|
|
rv = mBlockFile[i].Open(blockFile, blockSize);
|
|
if (NS_FAILED(rv)) goto error_exit;
|
|
}
|
|
return NS_OK;
|
|
|
|
error_exit:
|
|
(void)CloseBlockFiles(); // we already have an error to report
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::CloseBlockFiles()
|
|
{
|
|
nsresult rv, rv2 = NS_OK;
|
|
for (int i=0; i < 3; ++i) {
|
|
rv = mBlockFile[i].Close();
|
|
if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
|
|
}
|
|
return rv2;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record, nsDiskCacheEntry ** result)
|
|
{
|
|
nsresult rv = NS_ERROR_UNEXPECTED;
|
|
nsDiskCacheEntry * diskEntry = nsnull;
|
|
PRUint32 metaFile = record->MetaFile();
|
|
PRFileDesc * fd = nsnull;
|
|
*result = nsnull;
|
|
|
|
if (!record->MetaLocationInitialized()) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (metaFile == 0) { // entry/metadata stored in separate file
|
|
// open and read the file
|
|
nsCOMPtr<nsILocalFile> file;
|
|
rv = GetLocalFileForDiskCacheRecord(record, nsDiskCache::kMetaData, getter_AddRefs(file));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRFileDesc * fd = nsnull;
|
|
rv = file->OpenNSPRFileDesc(PR_RDONLY, 00666, &fd);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRInt32 fileSize = PR_Available(fd);
|
|
if (fileSize < 0) {
|
|
// XXX an error occurred. We could call PR_GetError(), but how would that help?
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
goto exit;
|
|
}
|
|
|
|
diskEntry = (nsDiskCacheEntry *) new char[fileSize];
|
|
if (!diskEntry) {
|
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
PRInt32 bytesRead = PR_Read(fd, diskEntry, fileSize);
|
|
if (bytesRead < fileSize) {
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
goto exit;
|
|
}
|
|
|
|
} else if (metaFile < 4) { // XXX magic number: use constant
|
|
// entry/metadata stored in cache block file
|
|
|
|
// allocate buffer
|
|
PRUint32 blockSize = GetBlockSizeForIndex(metaFile - 1);
|
|
PRUint32 blockCount = record->MetaBlockCount();
|
|
diskEntry = (nsDiskCacheEntry *) new char[blockSize * blockCount];
|
|
|
|
// read diskEntry
|
|
rv = mBlockFile[metaFile - 1].ReadBlocks((char *)diskEntry,
|
|
record->MetaStartBlock(),
|
|
blockCount);
|
|
if (NS_FAILED(rv)) goto exit;
|
|
}
|
|
|
|
diskEntry->Unswap(); // disk to memory
|
|
// pass ownership to caller
|
|
*result = diskEntry;
|
|
diskEntry = nsnull;
|
|
|
|
exit:
|
|
// XXX auto ptr would be nice
|
|
if (fd) (void) PR_Close(fd);
|
|
delete [] (char *)diskEntry;
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding);
|
|
if (!diskEntry) return NS_ERROR_UNEXPECTED;
|
|
|
|
PRUint32 size = diskEntry->Size();
|
|
PRUint32 fileIndex;
|
|
PRUint32 blocks = 0;
|
|
|
|
if (size < 1024) { // block size 256
|
|
fileIndex = 1;
|
|
blocks = size / 256 + 1;
|
|
} else if (size < 4096) { // block size 1024
|
|
fileIndex = 2;
|
|
blocks = size / 1024 + 1;
|
|
} else if (size < 16384) { // block size 4096
|
|
fileIndex = 3;
|
|
blocks = size / 4096 + 1;
|
|
} else { // separate file
|
|
fileIndex = 0;
|
|
}
|
|
|
|
PRUint32 metaFile = binding->mRecord.MetaFile();
|
|
|
|
// Deallocate old storage if necessary
|
|
if (binding->mRecord.MetaLocationInitialized()) {
|
|
// we have existing storage
|
|
|
|
if ((metaFile == 0) && (fileIndex == 0)) { // keeping the separate file
|
|
// just decrement total
|
|
// XXX if bindRecord.MetaFileSize == USHRT_MAX, stat the file to see how big it is
|
|
DecrementTotalSize(binding->mRecord.MetaFileSize() * 1024);
|
|
NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
|
|
"generations out of sync");
|
|
} else {
|
|
rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
|
|
if (NS_FAILED(rv)) goto exit;
|
|
}
|
|
}
|
|
|
|
binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
|
|
|
|
if (fileIndex == 0) {
|
|
// Write entry data to separate file
|
|
PRUint32 metaFileSizeK = ((size + 0x0399) >> 10); // round up to nearest 1k
|
|
nsCOMPtr<nsILocalFile> localFile;
|
|
|
|
// XXX handle metaFileSizeK > USHRT_MAX
|
|
binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
|
|
binding->mRecord.SetMetaFileSize(metaFileSizeK);
|
|
rv = UpdateRecord(&binding->mRecord);
|
|
if (NS_FAILED(rv)) goto exit;
|
|
|
|
rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
|
|
nsDiskCache::kMetaData,
|
|
getter_AddRefs(localFile));
|
|
if (NS_FAILED(rv)) goto exit;
|
|
|
|
// open the file
|
|
PRFileDesc * fd;
|
|
rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00666, &fd);
|
|
if (NS_FAILED(rv)) goto exit; // unable to open or create file
|
|
|
|
// write the file
|
|
diskEntry->Swap();
|
|
PRInt32 bytesWritten = PR_Write(fd, diskEntry, size);
|
|
|
|
PRStatus err = PR_Close(mMapFD);
|
|
if ((bytesWritten != (PRInt32)size) || (err != PR_SUCCESS)) {
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
goto exit;
|
|
}
|
|
// XXX handle metaFileSizeK == USHRT_MAX
|
|
IncrementTotalSize(metaFileSizeK * 1024);
|
|
|
|
} else {
|
|
// write entry data to disk cache block file
|
|
PRInt32 startBlock = mBlockFile[fileIndex - 1].AllocateBlocks(blocks);
|
|
if (startBlock < 0) {
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
goto exit;
|
|
}
|
|
|
|
// update binding and cache map record
|
|
binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
|
|
rv = UpdateRecord(&binding->mRecord);
|
|
if (NS_FAILED(rv)) goto exit;
|
|
// XXX we should probably write out bucket ourselves
|
|
|
|
// write data
|
|
diskEntry->Swap();
|
|
rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, startBlock, blocks);
|
|
if (NS_FAILED(rv)) goto exit;
|
|
|
|
IncrementTotalSize(blocks * GetBlockSizeForIndex(fileIndex - 1));
|
|
}
|
|
|
|
exit:
|
|
delete [] (char *)diskEntry;
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::DoomRecord(nsDiskCacheRecord * record)
|
|
{
|
|
nsresult rv = DeleteRecord(record);
|
|
// XXX future: add record to doomed record journal
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
|
|
{
|
|
nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
|
|
nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
|
|
return NS_FAILED(rv1) ? rv1 : rv2;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, PRBool metaData)
|
|
{
|
|
nsresult rv = NS_ERROR_UNEXPECTED;
|
|
PRUint32 fileIndex = metaData ? record->MetaFile() : record->DataFile();
|
|
nsCOMPtr<nsIFile> file;
|
|
|
|
if (fileIndex == 0) {
|
|
// delete the file
|
|
PRUint32 sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
|
|
// XXX if sizeK == USHRT_MAX, stat file for actual size
|
|
|
|
rv = GetFileForDiskCacheRecord(record, metaData, getter_AddRefs(file));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = file->Remove(PR_FALSE); // false == non-recursive
|
|
}
|
|
DecrementTotalSize(sizeK * 1024);
|
|
|
|
} else if (fileIndex < 4) {
|
|
// deallocate blocks
|
|
PRInt32 startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
|
|
PRInt32 blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
|
|
|
|
rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
|
|
DecrementTotalSize(blockCount * GetBlockSizeForIndex(fileIndex - 1));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::DeleteRecordAndStorage(nsDiskCacheRecord * record)
|
|
{
|
|
nsresult rv1 = DeleteStorage(record);
|
|
nsresult rv2 = DeleteRecord(record);
|
|
return NS_FAILED(rv1) ? rv1 : rv2;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
|
|
PRBool meta,
|
|
nsIFile ** result)
|
|
{
|
|
if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRInt16 generation = record->Generation();
|
|
char name[32];
|
|
::sprintf(name, "%08X%c%02X", record->HashNumber(), (meta ? 'm' : 'd'), generation);
|
|
rv = file->Append(name);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_IF_ADDREF(*result = file);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
|
|
PRBool meta,
|
|
nsILocalFile ** result)
|
|
{
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = GetFileForDiskCacheRecord(record, meta, getter_AddRefs(file));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_IF_ADDREF(*result = localFile);
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsDiskCacheMap::GetBlockFileForIndex(PRUint32 index, nsILocalFile ** result)
|
|
{
|
|
if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
char name[32];
|
|
::sprintf(name, "_CACHE_%03d_", index + 1);
|
|
rv = file->Append(name);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
|
|
NS_IF_ADDREF(*result = localFile);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
PRUint32
|
|
nsDiskCacheMap::GetBlockSizeForIndex(PRUint32 index)
|
|
{
|
|
return 256 << (2 * (index)); // XXX magic numbers
|
|
|
|
}
|